import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
在上面的示例中,每当调用setCount(count + 1)
时,都会发生重新渲染。我很想了解这个流程。
我试着查看源代码。我在github.com/facebook/reflect.上找不到任何关于useState
或其他钩子的参考
我通过npm i react@next
安装了react@next
,并在node_modules/react/cjs/react.development.js
上发现了以下内容
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
在追溯dispatcher.useState()
时,我只能找到以下内容。。。
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
var ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
我想知道在哪里可以找到dispatcher.useState()
实现,并了解当调用setState
setCount
时它是如何触发重新渲染的。
任何指针都会有所帮助。
谢谢!
理解这一点的关键是Hooks FAQ 中的以下段落
React如何将Hook调用与组件关联起来
React跟踪当前渲染组件。多亏了钩子规则,我们知道钩子只从React组件调用(或自定义钩子,也只从Reack组件调用)。
每个组件都有一个内部的"存储单元"列表。它们只是JavaScript对象,我们可以在其中放置一些数据。当调用类似于useState()的Hook时,它会读取当前单元格(或在第一次渲染时对其进行初始化),然后将指针移动到下一个单元格。这就是多个useState()调用每个get独立本地状态的方式。
(这也解释了钩子的规则。钩子需要以相同的顺序无条件调用,否则内存单元和钩子的关联会被打乱。)
让我们来看看你的反例,看看会发生什么。为了简单起见,我将参考编译的开发React源代码和React DOM源代码,这两个版本都是16.13.1。
当组件安装并且首次调用useState()
(在第1581行定义)时,该示例开始。
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
正如您所注意到的,这将调用resolveDispatcher()
(定义在第1546行)。dispatcher
在内部引用当前正在渲染的组件。在一个组件中(如果你敢被解雇),看看调度员,例如通过
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
如果您将此应用于反例,您会注意到dispatcher.useState()
引用了react dom代码。当首次安装组件时,useState
指的是在15986线上定义的调用mountState()
的那个。在重新呈现时,调度器已经改变,并且线路16077上的函数useState()
被触发,该函数调用updateState()
。第15352行的mountState()
和第15371行的updateState()
这两种方法都返回count, setCount
对。
追踪ReactCurrentDispatcher
变得相当混乱。然而,它的存在已经足以理解重新渲染是如何发生的。魔术发生在幕后。正如常见问题解答所述,React会跟踪当前渲染的组件。这意味着useState()
知道它连接到哪个组件,如何找到状态信息以及如何触发重新渲染。
我还试图以一种非常简单和基本的方式理解useState背后的逻辑,如果我们只研究它的基本功能,不包括优化和异步行为,那么我们发现它基本上有4个共同点,
- 维持状态,主要工作要做
- 重新呈现被调用的组件,以便调用方组件可以获得状态的最新值
- 由于它导致调用方组件的重新呈现,这意味着它也必须维护该组件的实例或上下文,这也允许我们同时对多个组件使用useState
- 因为我们可以在组件内自由使用任意数量的useState,这意味着它必须为同一组件内的每个useState保留一些标识
记住这些,我想出了下面的片段
const Demo = (function React() {
let workInProgress = false;
let context = null;
const internalRendering = (callingContext) => {
context = callingContext;
context();
};
const intialRender = (component) => {
context = component;
workInProgress = true;
context.state = [];
context.TotalcallerId = -1; // to store the count of total number of useState within a component
context.count = -1; // counter to keep track of useStates within component
internalRendering(context);
workInProgress = false;
context.TotalcallerId = context.count;
context = null;
};
const useState = (initState) => {
if (!context) throw new Error("Can only be called inside function");
// resetting the count so that it can maintain the order of useState being called
context.count =
context.count === context.TotalcallerId ? -1 : context.count;
let callId = ++context.count;
// will only initialize the value of setState on initial render
const setState =
!workInProgress ||
(() => {
const instanceCallerId = callId;
const memoizedContext = context;
return (updatedState) => {
memoizedContext.state[instanceCallerId].value = updatedState;
internalRendering(memoizedContext);
};
})();
context.state[callId] = context.state[callId] || {
value: initState,
setValue: setState,
};
return [context.state[callId].value, context.state[callId].setValue];
};
return { useState, intialRender };
})();
const { useState, intialRender } = Demo;
const Component = () => {
const [count, setCount] = useState(1);
const [greeting, setGreeting] = useState("hello");
const changeCount = () => setCount(100);
const changeGreeting = () => setGreeting("hi");
setTimeout(() => {
changeCount();
changeGreeting();
}, 5000);
return console.log(`count ${count} name ${greeting}`);
};
const anotherComponent = () => {
const [count, setCount] = useState(50);
const [value, setValue] = useState("World");
const changeCount = () => setCount(500);
const changeValue = () => setValue("React");
setTimeout(() => {
changeCount();
changeValue();
}, 10000);
return console.log(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);
此处useState和initialRender取自DemoinitialRender最初用于调用组件,它将首先初始化上下文,然后在该上下文上将状态设置为空数组(每个组件上都有多个useState所以我们需要数组来维护它),还需要计数器来计算每个useStateotalCounter来存储为每个组件调用的useState的总数。
setState
是Component/PureComponent
类上的一个方法,因此它将执行Component
类中实现的任何操作(包括调用render
方法)。
setState
将状态更新卸载到enqueueSetState
,因此它与之绑定的事实实际上只是使用类并从Component
扩展的结果。一旦您意识到状态更新实际上并不是由组件本身处理的,this
只是访问状态更新功能的一种方便方式,那么useState
不显式绑定到您的组件就更有意义了。
FunctionComponent不同。在过去,它们是纯粹的,简单的。但现在他们有了自己的状态。很容易忘记react使用createElement包装所有JSX节点,还包括FunctionComponent。
function FunctionComponent(){
return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
return React.createElement("div", null, "123");
}
var a = React.createElement(FunctionComponent, null);
传递FunctionComponent进行反应。当调用setState时,很容易重新渲染;