当前位置:网站首页>03 | implement usereducer and usestate
03 | implement usereducer and usestate
2022-07-26 17:19:00 【Oriental pajamas】
Use useReducer
Before that , Need to be in demo/which-react.js Lieutenant general useReducer from react Introduction in , And will ReactDOM Replace with from react-dom introduce
import { Component, Fragment, useReducer } from 'react';
import ReactDOM from 'react-dom/client';
// import { Component, Fragment } from '../src/react';
// import ReactDOM from '../src/react-dom';
export {Component,Fragment,useReducer,ReactDOM
}
stay demo/src/main.jsx Medium FunctionComponent Add useReducer
function FunctionComponent(props) {const [state, dispatch] = useReducer(x => x + 1, 0)return (<div className='function'><p>{props.name}</p><div>{state}</div><button onClick={dispatch} >+1</button></div>)
}
So every click button Will be state + 1, With the effect we want , Next, let's implement it
Realization useReducer
take React The introduction of is reset to be written by oneself ( modify which-react.js), establish src/ReactFiberHooks.ts
export function useReducer(reducer, initalState) {const dispatch = () => {console.log('useReducer dispatch log')}// Return directly to return [initalState, dispatch]
}
Then you will find that you click button There is no trigger dispatch Inside console, This is because we have not yet achieved React event , For the time being, just hang the event as an attribute dom On
// src/utils.ts
export function updateNode(node, nextVal) {Object.keys(nextVal).forEach(key => {if (key === 'children') {if (isStringOrNumber(nextVal[key])) {node.textContent = nextVal[key]}} else {node[key] = nextVal[key] // Here, it is directly put as an attribute dom Yes }})
}
Let's deal with it briefly , Can make the event responsive ( Notice that this is not really React Event implementation )
export function updateNode(node: HTMLElement, nextVal) {Object.keys(nextVal).forEach(key => {if (key === 'children') {if (isStringOrNumber(nextVal[key])) {node.textContent = nextVal[key]}} else if (key.slice(0, 2) === 'on') {// Simply handle the event response ( Not really React event )const eventName = key.slice(2).toLocaleLowerCase()node.addEventListener(eventName, nextVal[key])} else {node[key] = nextVal[key]}})
}
Click the button to make console.log('useReducer dispatch log') The execution came out
Realization mount At the time of the useReducer
import { Fiber } from "./ReactFiber"
interface Hook {memoizedState: any, // statenext: null | Hook // next hook
}
// Currently rendering fiber
let currentlyRenderingFiber: Fiber | null = null
// Nothing special , I just want to return one Fiber type Not allow ts Report errors
function getCurrentlyRenderingFiber() {return currentlyRenderingFiber as Fiber
}
// Currently in process hook
let workInProgressHook: Hook | null = null
export function renderWithHooks(workInProgress: Fiber) {currentlyRenderingFiber = workInProgresscurrentlyRenderingFiber.memoizedState = nullworkInProgressHook = null
}
function updateWorkInProgressHook() {currentlyRenderingFiber = getCurrentlyRenderingFiber()let hookconst current = currentlyRenderingFiber.alternate// current The description of existence is update, Otherwise, it would be mountif (current) {// Before reuse hookcurrentlyRenderingFiber.memoizedState = current.memoizedState// See if it's the first hookif (workInProgressHook) {// No , Get the next one hook, Simultaneous updating workInProgressHookworkInProgressHook = hook = workInProgressHook.next} else {// Is the first hook , Get the first one hookworkInProgressHook = hook = currentlyRenderingFiber.memoizedState}} else {// mount You need to create a new hookhook = {memoizedState: null, // statenext: null // next hook}if (workInProgressHook) {workInProgressHook = workInProgressHook.next = hook} else {// first hook, take hook Put it in fiber Of state On , Simultaneous updating workInProgressHookworkInProgressHook = currentlyRenderingFiber.memoizedState = hook}}// Eventually return hook( That is to say workInProgressHook)return hook
}
export function useReducer(reducer, initalState) {currentlyRenderingFiber = getCurrentlyRenderingFiber()const hook = updateWorkInProgressHook()if (!currentlyRenderingFiber.alternate) {// Put the default data into... When rendering for the first time hook.memoizedState You can go up. hook.memoizedState = initalState}const dispatch = () => {console.log('useReducer dispatch log')}// return statereturn [hook.memoizedState, dispatch]
}
Processing FunctionComponent When updating the fiber( That is, the current function )
// ReactFiberReconciler.ts
export function updateFunctionComponent(workInProgress: Fiber) {// Update in progress fiberrenderWithHooks(workInProgress)const { type, props } = workInProgressconst children = type(props)reconcileChildren(workInProgress, children)
}
Realization update At the time of the useReducer
Now the page renders normally in the browser , No report error , Clicking a button is just console, Next we deal with update when . update You need to update the data ( Page update ), Before, it was through adjustment scheduleUpdateOnFiber updated , It also needs to be used here
export function useReducer(reducer, initalState) {const hook = updateWorkInProgressHook()if (!currentlyRenderingFiber?.alternate) {// First rendering hook.memoizedState = initalState}const dispatch = () => {// Modify the status value ( Put the old state To the user , And then return the new state to hook)hook.memoizedState = reducer(hook.memoizedState); // There are parentheses after it , Need semicolon // Before updating currentlyRenderingFiber Set it to your own alternate (currentlyRenderingFiber as Fiber).alternate = { ...currentlyRenderingFiber as Fiber }// to update scheduleUpdateOnFiber(currentlyRenderingFiber as Fiber)console.log('useReducer dispatch log')}return [hook.memoizedState, dispatch]
}
Because we wrote FragmentComponent It's also FunctionComponent So in mount After finishing currentlyReeringFiber It points to this FragmentComponent, Make sure you write the last one here FunctionComponent It contains just written useReducer The components of , Look away at all the other components , We will deal with other problems later .
Refresh the page , Click the next page from this FunctionComponent Later components will appear again , meanwhile state It's the latest value , This is because we are reconcileChildren Created when Fiber Of flags yes Placement, Each time a new one is created dom
Realize the reuse of nodes
because diff More complicated , Our focus in this section is to achieve useReducer, So we will only implement a simple diff(sameNode Function to determine whether it can be reused )
function reconcileChildren(workInProgress: Fiber, children) {if (isStringOrNumber(children)) {return}// Here, the child nodes are treated as arrays const newChildren: any[] = isArray(children) ? children : [children]// oldFiber The head node of let oldFiber = workInProgress.alternate?.child// Used to save the last fiber node let previousNewFiber: Fiber | null = nullfor (let i = 0; i < newChildren.length; i++) {const newChild = newChildren[i]if (newChild === null) {// Will meet null The node of , Just ignore it continue}const newFiber = createFiber(newChild, workInProgress)// Can I reuse const same = sameNode(newFiber, oldFiber)if (same) {// Can reuse Object.assign(newFiber, {stateNode: (oldFiber as Fiber).stateNode,alternate: oldFiber as Fiber,flags: Update})}if (oldFiber) {// be in for in ,oldFiber Also need to update to the next fiber oldFiber = oldFiber.sibling}if (previousNewFiber === null) {// The first child node is saved directly to workInProgress On workInProgress.child = newFiber} else {// All subsequent saved to the previous node sibling On previousNewFiber.sibling = newFiber}// to update previousNewFiber = newFiber}
}
// Node reuse conditions
// 1. At the same level
// 2. type identical
// 3. key identical
function sameNode(a, b) {return a && b && a.type === b.type && a.key === b.key
}
And then commit You need to handle the situation when the node is updated
// ReactFIberWorkLoop.ts
function commitWorker(workInProgress: Fiber | null) {// ......if (flags & Update && stateNode) {// Update properties updateNode(stateNode, (workInProgress.alternate as Fiber).props, workInProgress.props)}// ......
}
stay updateNode In the function, you need to remove both the old attributes and the events you were listening to , Reassign new properties and listen for events
// src/utils.ts
export function updateNode(node: HTMLElement, prevVal, nextVal) {// Go through the old props, The original event and the new props Nonexistent attribute removed Object.keys(prevVal).forEach((key) => {if (key === "children") {// It could be text , Clear it directly if (isStringOrNumber(prevVal[key])) {node.textContent = "";}} else if (key.slice(0, 2) === "on") {// The event needs to be removed const eventName = key.slice(2).toLocaleLowerCase();node.removeEventListener(eventName, prevVal[key]);} else {// For the old key There is oldProps, New ones that do not exist above need to be disposed of (remove)if (!(key in nextVal)) {node[key] = "";}}});// to update propsObject.keys(nextVal).forEach((key) => {if (key === "children") {// It could be text if (isStringOrNumber(nextVal[key])) {node.textContent = nextVal[key] + "";}} else if (key.slice(0, 2) === "on") {const eventName = key.slice(2).toLocaleLowerCase();node.addEventListener(eventName, nextVal[key]);} else {node[key] = nextVal[key];}});
}
Function components useReducer To achieve the update . But there are still some problems to be solved bug.
We found that , If there are multiple FunctionComponent when , because currntkyRendingFiber Is a global variable , Leading to the final point is the last FunctionComponent, Make our state, Cannot be updated , Instead, the last component is re rendered .
So we need to make sure that we are using dispatch Internal fiber Is the name of the current component fiber. Then we can use bind Properties that can store parameters , take currentlyRenderingFiber As bind Prefabrication parameters at , Makes us call dispatch When I got it fiber It's using this hook Of fiber.
// src/ReactFiberHooks.ts
export function useReducer(reducer, initalState) {const hook = updateWorkInProgressHook()if (!(currentlyRenderingFiber as Fiber).alternate) {// First rendering hook.memoizedState = initalState}// because currentlyRenderingFiber Global variable , May cause stored fiber It doesn't need to be updated state Of fiber// So you have to go through bind stay dispatchReducerAction This function stores relevant information in memory (fiber etc. ), When calling, you can get the information that needs to be updated fiberconst dispatch = dispatchReducerAction.bind(null,currentlyRenderingFiber,hook,reducer)return [hook.memoizedState, dispatch]
}
function dispatchReducerAction(fiber: Fiber, hook: Hook, reducer, action) {hook.memoizedState = reducer(hook.memoizedState)// Before updating currentlyRenderingFiber Set it to your own alternate fiber.alternate = { ...fiber }// to update scheduleUpdateOnFiber(fiber)
}
state The problem of not updating has been solved , There is also the problem of component re rendering . This is also very simple . Because we only need the current component update , Other components do not need to be moved , So we are scheduleUpdateOnFiber You only need to submit the current fiber that will do
function dispatchReducerAction(fiber: Fiber, hook: Hook, reducer, action) {hook.memoizedState = reducer(hook.memoizedState)fiber.alternate = { ...fiber }// Because we only update this one fiber Components , Cannot affect other components , So we need to sibling Set to null, Avoid subsequent components being rendered repeatedly fiber.sibling = null// Update current fiberscheduleUpdateOnFiber(fiber)
}
thus , We finished useReducer The function of . In this function , We did fiber、dom Reuse and update of
Realization useState
It's done useReducer, Will find useState It's almost the same , The difference is that useState No, reducer Parameters , And using dispatch The new value will be passed in , We are right. useReducer After a little modification, it is compatible to realize useState
export function useState(initalState) {// Because we didn't reduce , So pass on nullreturn useReducer(null, initalState)
}
function dispatchReducerAction(fiber: Fiber, hook: Hook, reducer, action) {// By judging whether there is reducer, To decide state The value of is reducer Results of execution , It depends on the parameters passed in // In fact, judgment is useReducer still useStatehook.memoizedState = reducer ? reducer(hook.memoizedState) : actionfiber.alternate = { ...fiber }fiber.sibling = nullscheduleUpdateOnFiber(fiber)
}
Optimize useReducer
In our actual use useReducer when , It may be used as follows , Calling dispatch You also need to support parameters .
function reducer(state, action) {switch (action.type) {case 'increment':return { count: state.count + 1 };case 'decrement':return { count: state.count - 1 };default:throw new Error();}
}
function FunctionComponent(props) {const [data, setData] = useReducer(reducer, { count: 0 })return (<div className='function'><div>{ data.count }</div><button onClick={ () => setData({ type: 'increment' }) }>data.count + 1</button><button onClick={ () => setData({ type: 'decrement' }) }>data.count - 1</button></div>)
}
We just need to put dispatch Medium action The incoming to reducer Then you can
function dispatchReducerAction(fiber: Fiber, hook: Hook, reducer, action) {// By judging whether there is reducer, To decide state The value of is reducer Results of execution , It depends on the parameters passed in // In fact, judgment is useReducer still useStatehook.memoizedState = reducer ? reducer(hook.memoizedState, action) : actionfiber.alternate = { ...fiber }fiber.sibling = nullscheduleUpdateOnFiber(fiber)
}
thus , We did useReducer and useState, Provides the ability to update page data .
Warehouse address , Kneel down and ask for your help , Thank you very much! ~ PS: The code in this section is v0.0.3 Branch
边栏推荐
- Redis hotspot key and big value
- Win11怎么重新安装系统?
- “青出于蓝胜于蓝”,为何藏宝计划(TPC)是持币生息最后的一朵白莲花
- "Green is better than blue". Why is TPC the last white lotus to earn interest with money
- Heavy! Zeng Xuezhong was promoted to vice chairman and CEO of zhanrui, and Chu Qingren was appointed as co CEO!
- 2019普及组总结
- [basic course of flight control development 2] crazy shell · open source formation UAV - timer (LED flight information light and indicator light flash)
- Stand aside with four and five rear cameras, LG or push the 16 rear camera mobile phone!
- Linear regression from zero sum using mxnet
- Tcpdump命令详解
猜你喜欢

Recurrence of historical loopholes in ThinkPHP

Thoroughly uncover how epoll realizes IO multiplexing

PyQt5快速开发与实战 3.4 信号与槽关联

Quickly build a development platform for enterprise applications

Small application of C language using structure to simulate election

In May, 2022, video user insight: user use time increased, and the platform achieved initial results in cost reduction and efficiency increase

What kind of product is the Jetson nano? (how about the performance of Jetson nano)

In the first half of the year, sales increased by 10% against the trend. You can always trust Volvo, which is persistent and safe

Can TCP and UDP use the same port?

Matlab paper illustration drawing template issue 40 - pie chart with offset sector
随机推荐
After Australia, New Zealand announced the ban on Huawei 5g! Huawei official response
Chuan Hejing technology's mainland factory was invaded by a virus, and the whole line was shut down!
What is a distributed timed task framework?
[development tutorial 7] crazy shell · open source Bluetooth heart rate waterproof sports Bracelet - capacitive touch
[C language classic topic exercise 2]
Detailed explanation of openwrt's feeds.conf.default
regular expression
大家下午好,请教一个问题:如何从保存点启动一个之前以SQL提交的作业?问题描述:用SQL在cl
【开发教程9】疯壳·ARM功能手机-I2C教程
[express receives get, post, and route request parameters]
Win11系统如何一键进行重装?
Why are test / development programmers who are better paid than me? Abandoned by the times
中金证券vip账户找谁开安全啊?
37. [categories of overloaded operators]
My meeting of OA project (meeting seating & submission for approval)
How emqx 5.0 under the new architecture of mria+rlog realizes 100million mqtt connections
Linear regression from zero sum using mxnet
[Development Tutorial 9] crazy shell arm function mobile phone-i2c tutorial
The first case in Guangdong! A company in Guangzhou was punished by the police for failing to fulfill its obligation of data security protection
OpenWrt之feeds.conf.default详解