sar*_*tam 11 javascript reactjs react-hooks
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>
);
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,无论何时setCount(count + 1)
调用都会发生重新渲染.我很想学习这个流程.
我试着查看源代码.我useState
在github.com/facebook/react找不到任何引用或其他钩子.
我安装了react@next
via npm i react@next
,发现了以下内容node_modules/react/cjs/react.development.js
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
Run Code Online (Sandbox Code Playgroud)
在追溯时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;
}
Run Code Online (Sandbox Code Playgroud)
var ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
Run Code Online (Sandbox Code Playgroud)
我想知道在哪里可以找到dispatcher.useState()
实现并了解它在调用时如何触发重新渲染.setState
setCount
任何指针都会有所帮助.
谢谢!
理解这一点的关键是来自Hooks FAQ的以下段落
React 如何将 Hook 调用与组件关联起来?
React 会跟踪当前呈现的组件。多亏了 Hooks 规则,我们知道 Hooks 只能从 React 组件(或自定义 Hooks — 也只能从 React 组件调用)中调用。
每个组件都有一个内部“存储单元”列表。它们只是 JavaScript 对象,我们可以在其中放置一些数据。当您调用像 useState() 这样的 Hook 时,它会读取当前单元格(或在第一次渲染期间对其进行初始化),然后将指针移动到下一个单元格。这就是多个 useState() 调用每个获取独立本地状态的方式。
(这也解释了Hooks的规则。Hooks 需要以相同的顺序无条件调用,否则memory cell和 hook的关联就搞砸了。)
让我们来看看你的反例,看看会发生什么。为简单起见,我将参考已编译的开发 React 源代码和React DOM 源代码,均为 16.13.1 版本。
该示例在组件安装和useState()
(在第 1581 行定义)第一次被调用时开始。
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
Run Code Online (Sandbox Code Playgroud)
正如您所注意到的,这个调用resolveDispatcher()
(在第 1546 行定义)。该dispatcher
内部是指,因此目前所呈现的组件。在组件中,您可以(如果您敢被解雇)查看调度程序,例如通过
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
Run Code Online (Sandbox Code Playgroud)
如果您在反例中应用它,您会注意到 指的dispatcher.useState()
是react-dom代码。第一次安装组件时,useState
指的是在 15986 行中定义的调用mountState()
. 重新渲染时,调度程序发生了变化,并useState()
触发了第 16077 行的函数,该函数调用updateState()
. mountState()
第 15352 行和updateState()
第 15371行的两种方法都返回该count, setCount
对。
跟踪ReactCurrentDispatcher
变得相当混乱。然而,它存在的事实已经足以理解重新渲染是如何发生的。在魔术发生在幕后。正如 FAQ 所述,React 会跟踪当前呈现的组件。这意味着,useState()
知道它附加到哪个组件,如何找到状态信息以及如何触发重新渲染。
小智 5
我还尝试以一种非常简单和基本的方式理解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);
Run Code Online (Sandbox Code Playgroud)
这里的useState和initialRender取自Demo。intialRender用于最初调用组件,它会首先初始化上下文,然后在该上下文上将状态设置为空数组(每个组件上有多个useState,因此我们需要数组来维护它),并且我们还需要计数器来制作对每个useState进行计数,并使用 TotalCounter来存储每个组件调用的useState总数。
setState
是类上的一个方法Component/PureComponent
,因此它将执行Component
类中实现的任何操作(包括调用该render
方法)。
setState
卸载状态更新,enqueueSetState
因此它绑定到 this 的事实实际上只是使用类并从Component
. 一旦您意识到状态更新实际上并不是由组件本身处理,而这this
只是访问状态更新功能的一种便捷方式,那么useState
不显式绑定到组件就更有意义了。
归档时间: |
|
查看次数: |
2769 次 |
最近记录: |