This is the last article in the series, finally concluding 🐶.
React Hooks completely change the way Class Component is written, further improving state reuse and allowing Function Component to have internal state. Personally, I like them better. Of course, if you are an experienced user of Class Component, you will find it difficult to get started. After all, a lot of HOC and Render Props are basically unusable. And the fact that Function Component was stateless with no side effects now has the ability to import state via Hooks is really confusing. Another advantage of Function Component is that you can completely say goodbye to this, which is a real pain in the ass in Class Component 😶.
How are hooks associated with components
As mentioned several times in previous articles, update Ue and effectList are linked list data structures mounted on Fiber nodes. In a function component, all Hooks are stored in a linked list and eventually mounted to Fiber. memoizedState.
function App() {
const [num, updateNum] = useState(0)
return <div
onClick={()= > updateNum(num => num + 1)}
>{ num }</div>
}
export default App
Copy the code
Let’s take a quick look at how to construct a linked list when useState is called:
var workInProgressHook = null
var HooksDispatcherOnMount = {
useState: function (initialState) {
return mountState(initialState)
}
}
function function mountState(initialState) {/ / newHooknodevar hook = mountWorkInProgressHook() // Cache the initial valuehook.memoizedState = initialState// Construct the update queue, similar tofiber.updateQueue
var queue = hook.queue = {
pending: null.dispatch: null.lastRenderedState: initialState
}
// Used to distribute updates
var dispatch = queue.dispatch = dispatchAction.bind(
null, workInProgress, queue
)
// [num, updateNum] = useState(0)
return [hook.memoizedState, dispatch]
}
function mountWorkInProgressHook() {
var hook = {
memoizedState: null.baseState: null.baseQueue: null.queue: null.next: null
}
if (workInProgressHook === null) {
// Construct the list head node
workInProgress.memoizedState = workInProgressHook = hook
} else {
// If the list already exists, mount it to next
workInProgressHook = workInProgressHook.next = hook
}
return workInProgressHook
}
Copy the code
If there are two hooks, the second Hook is mounted to the next property of the first Hook.
function App() {
const [num, updateNum] = useState(0)
const [str, updateStr] = useState('value: ')
return <div
onClick={()= > updateNum(num => num + 1)}
>{ str } { num }</div>
}
export default App
Copy the code
Update queue for Hook
Hooks are connected to each other via.next, and each Hook object has a queue. This queue is an updateQueue, just like the updateQueue on the Fiber node. In the React Fiber architecture, update queues are stored in linked lists.
class App extends React.Component {
state = { val: 0 }
click () {
for (let i = 0; i < 3; i++) {
this.setState({ val: this.state.val + 1}}})render() {
return <div onClick={()= > {
this.click()
}}>val: { this.state.val }</div>}}Copy the code
After clicking div, the three setstates generated are mounted to fiber.updateQueue in a linked list. When the MessageChannel receives the notification and actually performs the update operation, the updateQueue is retrieved. Update the calculation results to Fiber. memoizedState.
The logic of hook. Queue is exactly the same as that of Fiber. Update Ue.
function App() {
const [num, updateNum] = useState(0)
return <div
onClick={()= >{// Three consecutive updates updateNum(num => num + 1) updateNum(num => num + 1)}} > {num}</div>
}
export default App;
Copy the code
var dispatch = queue.dispatch = dispatchAction.bind(
null, workInProgress, queue
)
// [num, updateNum] = useState(0)
return [hook.memoizedState, dispatch]
Copy the code
When useState is called, the second parameter of the array returned is dispatch, which is obtained by dispatchAction bind.
function dispatchAction(fiber, queue, action) {
var update = {
next: null.action: action,
// omit scheduling parameters...
};
var pending = queue.pending
if (pending === null) {
update.next = update
} else {
update.next = pending.next
pending.next = update
}
queue.pending = update
// Perform the update
scheduleUpdateOnFiber()
}
Copy the code
You can see that the linked list is constructed in much the same way as fiber. Update Ue. Num = num; num = num; num = num;
Function component updates
As shared in the previous article, the update process in Fiber architecture is divided into two steps: beginWork and completeWork. In beginWork, sub-components are constructed by render operation according to component type.
function beginWork(current, workInProgress) {
switch (workInProgress.tag) {
// Other types of component code omitted...
case FunctionComponent: {
// Where type is the function of the function component
// For example, the previous App component type is function App() {}
var Component = workInProgress.type
var resolvedProps = workInProgress.pendingProps
// Component update
return updateFunctionComponent(
current, workInProgress, Component, resolvedProps
)
}
}
}
function updateFunctionComponent(
current, workInProgress, Component, nextProps
) {
// Construct the child component
var nextChildren = renderWithHooks(
current, workInProgress, Component, nextProps
)
reconcileChildren(current, workInProgress, nextChildren)
return workInProgress.child
}
Copy the code
As the name suggests, the renderWithHooks method constructs a child component with Hooks.
function renderWithHooks(
current, workInProgress, Component, props
) {
if(current ! = =null&& current.memoizedState ! = =null) {
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate
} else {
ReactCurrentDispatcher.current = HooksDispatcherOnMount
}
var children = Component(props)
return children
}
Copy the code
As you can see from the above code, when a function component is updated or rendered for the first time, the function is essentially taken out and executed. The difference is that ReactCurrentDispatcher is assigned a different value, and the value of ReactCurrentDispatcher ultimately affects useState to call a different method.
According to the dual caching mechanism discussed in the previous article, the presence of current indicates an update operation, and the absence of current indicates the first rendering.
function useState(initialState) {
// Point to HooksDispatcherOnMount for the first rendering
// Point to HooksDispatcherOnUpdate
var dispatcher = ReactCurrentDispatcher.current
return dispatcher.useState(initialState)
}
Copy the code
HooksDispatcherOnMount. UseState in front of the code have been introduced, no longer is introduced here.
// The HooksDispatcherOnMount code was introduced earlier
var HooksDispatcherOnMount = {
useState: function (initialState) {
return mountState(initialState)
}
}
Copy the code
We look at HooksDispatcherOnMount useState logic.
var HooksDispatcherOnUpdateInDEV = {
useState: function (initialState) {
return updateState()
}
}
function updateState() {
// Retrieve the current hook
workInProgressHook = nextWorkInProgressHook
nextWorkInProgressHook = workInProgressHook.next
var hook = nextWorkInProgressHook
var queue = hook.queue
var pendingQueue = queue.pending
// Handle updates
var first = pendingQueue.next
var state = hook.memoizedState
var update = first
do {
var action = update.action
state = typeof action === 'function' ? action(state) : action
update = update.next;
} while(update ! = =null&& update ! == first) hook.memoizedState = statevar dispatch = queue.dispatch
return [hook.memoizedState, dispatch]
}
Copy the code
If you look at the setState code, the logic is the same. Take the action of the update object, execute it if it is a function, and replace state directly if it is not.
conclusion
This article is the simplest in the React series. If you want to get rid of the React source code, read the following article: The React Hooks Principle. In order to update in a circular manner, all local structures involving data have been changed to linked lists. This has the advantage of being able to interrupt at any time to make way for asynchronous mode. Fiber tree is like a Christmas tree. It’s full of alternate lights (EffectList, updateQueue, Hooks).
I recommend you to read this series from beginning to end, I believe it will be particularly fruitful.
- React architecture evolution – from synchronous to asynchronous
- Evolution of the React architecture – from recursion to loop
- React architecture evolution – Update mechanism