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/react找不到useState
或其他挂钩的引用。
我通过react@next
安装了npm i 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
时如何触发重新渲染。
任何指针都会有所帮助。
谢谢!
答案 0 :(得分:3)
了解这一点的关键是Hooks FAQ
中的以下段落React如何将Hook调用与组件相关联?
React跟踪当前渲染的组件。多亏了挂钩规则,我们知道挂钩只能从React组件(或自定义挂钩-也只能从React组件调用)中获得。
内部有一个与每个组件关联的“内存单元”列表。它们只是JavaScript对象,我们可以在其中放置一些数据。当您调用诸如useState()之类的Hook时,它将读取当前单元格(或在第一个渲染期间对其进行初始化),然后将指针移至下一个单元格。这就是多个useState()分别调用时如何获得独立的本地状态的方法。
(这也解释了Rules of Hooks。挂钩必须以相同的顺序无条件地调用,否则内存单元格和挂钩的关联会混乱。)
让我们看一下您的反例,看看会发生什么。为简单起见,我将同时参考两个版本16.13.1的compiled development React source code和React DOM source code。
该示例在安装组件并首次调用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()
知道它附加到哪个组件,如何查找状态信息以及如何触发重新呈现。
答案 1 :(得分:2)
setState
是Component/PureComponent
类的方法,因此它将执行Component
类中实现的所有操作(包括调用render
方法)。
setState
将状态更新卸载到enqueueSetState
中,因此绑定到此状态的事实实际上仅是使用类并从Component
扩展的结果。一次,您意识到状态更新实际上不是由组件本身处理的,并且this
只是访问状态更新功能的便捷方式,因此useState
并未明确绑定到组件更有意义。
答案 2 :(得分:2)
答案 3 :(得分:2)
我还尝试以一种非常简单和基本的方式来理解 useState 背后的逻辑,如果我们仅查看其基本功能(不包括优化和异步行为),那么我们发现它基本上在做4个共同点,
记住这些事情,我想出了以下代码段
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 来自Demo。 intialRender 最初用于调用组件,它将首先初始化 context ,然后在该上下文上将 state 设置为空数组(每个组件上有多个 useState ,因此我们需要数组来对其进行维护),还需要 counter 来为每个 useState 和 TotalCounter 存储每个组件被调用的 useState 的总数。
答案 4 :(得分:0)
FunctionComponent不同。过去,它们是纯净的,简单的。但是现在他们有了自己的状态。 容易忘记,使用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时,很容易重新渲染;