实现 useState
src/index.js
注意:我们这里写的暂时会有数组塌陷的情况。
其实因为在原版代码里,每一个组件里都有自己的index和数组。
在原版代理里,它是把这个放在了 fiber 里。
React有一个特点,每次 setState 更新都需要从根节点开始进行一次比较,不会按需更新,这也是React 耗时长,性能差的原因,正因为次,React 搞出了 fiber,fiber 的核心就是要 diff 工作可以暂停。
import React, { Component } from './react';
import ReactDOM from './react-dom';
// setState参数为 值
function Counter1(){
let [number, setNumber] = React.useState(5);
return (
<div>
<p>Counter1:{number}</p>
<button onClick={() => setNumber(number+1)}>Counter1:+</button>
</div>
)
}
// setState参数为 函数
function Counter2(){
let [number, setNumber] = React.useState(10);
return (
<div>
<p>Counter2:{number}</p>
<button onClick={() => setNumber(n => n+1)}>Counter2:+</button>
</div>
)
}
function Counter3(){
let [number, setNumber] = React.useState(15);
return (
<div>
<p>Counter3:{number}</p>
<button onClick={() => setNumber(number+1)}>Counter3:+</button>
</div>
)
}
function App(){
let [number, setNumber] = React.useState(0);
return (
<div>
<Counter1 />
{number%2 === 0 && <Counter2 />}
<Counter3 />
<button onClick={() => setNumber(number+1)}>App:+</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('root'));
src/react.js
import { useState } from './react-dom';
export { useState };
const React = {
useState,
}
export default React;
src/react-dom.js
+ let hookStates = []; // 这是一个数组,里面存放着我们所有的 hook 状态
+ let hookIndex = 0; // 当前 hook 的索引
+ let scheduleUpdate; // hook调度更新函数
// 给根容器挂载的时候
function render (vdom, container){
mount(vdom, container);
+ scheduleUpdate = () => {
+ hookIndex = 0; //在状态修改后调试更新的时候,把 hooks 的索引重置
+ compareTwoVdom(container, vdom, vdom); //虚拟DOM比较更新
+ }
}
// 挂载
function mount (vdom, container){
if (!vdom) return;
const dom = createDOM(vdom, container);
if (dom) container.appendChild(dom);
dom.componentDidMount && dom.componentDidMount();
}
/** 让函数组件可以使用状态
*
* @param {*} initialState 初始状态
*/
export function useState(initialState){
//把老的值取出来,如果没有,就使用默认值
hookStates[hookIndex] = hookStates[hookIndex] ||
(typeof initialState === 'function' ? initialState() : initialState);
// 因为 hookIndex 一直在变,
// 给每一个 useState 的 hook 定死一个不变的索引。用于 setState时 更新该hook的数据
let currentIndex = hookIndex;
function setState(newState){
if(typeof newState === 'function') newState = newState(hookStates[currentIndex]);
hookStates[currentIndex] = newState;
scheduleUpdate(); //当状态改变后要重新更新应用
}
return [hookStates[hookIndex++], setState]; //注意,先取值,再++,把 hook 索引指向下一个 useState。
}
实现 useMemo、useCallback
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#jncy9
src/react.js
import { useState, useCallback, useMemo } from './react-dom';
export { useState, useCallback, useMemo };
const React = {
useCallback,
useMemo,
}
export default React;
src/react-dom.js
/** 缓存 对象
*
* @param {*} factory 工厂方法,该方法返回要缓存的对象
* @param {*} deps 依赖数组
* @returns
*/
export function useMemo(factory, deps){
if (hookStates[hookIndex]){
let [lastMemo, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same){
hookIndex ++;
return lastMemo;
} else {
let newMemo = factory();
hookStates[hookIndex++] = [newMemo, deps];
return newMemo;
}
} else {
let newMemo = factory();
hookStates[hookIndex++] = [newMemo, deps];
return newMemo;
}
}
/** 缓存 函数
*
* @param {*} callback 要缓存的函数
* @param {*} deps 依赖数组
* @returns
*/
export function useCallback(callback, deps){
if (hookStates[hookIndex]){
let [lastCallback, lastDeps] = hookStates[hookIndex];
let same = deps.every((item, index) => item === lastDeps[index]);
if (same){
hookIndex ++;
return lastCallback;
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
} else {
hookStates[hookIndex++] = [callback, deps];
return callback;
}
}
实现 useReducer
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#YzW9B
src/react.js
import { useReducer } from './react-dom';
export { useReducer };
const React = {
useReducer,
}
export default React;
src/react-dom.js
/** 让函数组件可以使用状态,useState 可以算作 useReducer 的一个语法糖
*
* @param {*} initialState 初始状态
*/
export function useState(initialState){
return useReducer(null, initialState);
}
export function useReducer(reducer, initialState){
//把老的值取出来,如果没有,就使用默认值
let lastState = hookStates[hookIndex] = hookStates[hookIndex] ||
(typeof initialState === 'function' ? initialState() : initialState);
//因为 hookIndex 一直在变,
//给每一个 useState 的 hook 定死一个不变的索引。用于调用 setState(dispatch) 函数时 更新该hook的数据
let currentIndex = hookIndex;
function dispatch(action){
let nextState;
if (reducer){
nextState = reducer(lastState, action);
} else {
if (typeof action === 'function') action = action(lastState);
nextState = action;
}
hookStates[currentIndex] = nextState;
scheduleUpdate(); //当状态改变后要重新更新应用
}
//注意,先取值,再++,把 hook 索引指向下一个 useState。
return [hookStates[hookIndex++], dispatch];
}
实现 useContext
demo:https://www.yuque.com/zhuchaoyang/cx9erp/og7367#tCIIp
src/react.js
export function useContext(context){
return context._currentValue;
}
const React = {
useContext,
}
export default React;
实现 useEffect
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#C082E
src/react.js
import { useEffect } from './react-dom';
export { useEffect };
const React = {
useEffect,
}
export default React;
src/react-dom.js
export function compareTwoVdom(container, oldVdom, newVdom, nextDOM){
// 老的有,新的没有 => 要刪除此节点
if (oldVdom && !newVdom){
+ if (hookStates[hookIndex]){
+ let [destroyFunction] = hookStates[hookIndex];
+ destroyFunction && destroyFunction();
+ }
}
// 新旧都有虚拟DOM,但是类型type不相同 => 直接替换节点
if (oldVdom && newVdom && oldVdom.type !== newVdom.type){
+ if (hookStates[hookIndex]){
+ let [destroyFunction] = hookStates[hookIndex];
+ destroyFunction && destroyFunction();
+ }
}
}
/** useEffect
*
* 为了保证次回调函数不是同步执行,而是在页面渲染后执行,可以把它包装成宏任务
* @param {*} callback 回调函数
* @param {*} deps 依赖数组
*/
export function useEffect(callback, deps){
if (hookStates[hookIndex]){
let [destroyFunction, lastDeps] = hookStates[hookIndex];
let same = deps && deps.every((item, index) => item === lastDeps[index]);
if (same){
hookIndex ++;
} else {
destroyFunction && destroyFunction();
setTimeout(() => {
let destroyFunction = callback();
hookStates[hookIndex++] = [destroyFunction, deps];
})
}
} else {
// 用 setTimeout 封装成宏任务
setTimeout(() => {
let destroyFunction = callback(); //执行后,返回销毁函数
hookStates[hookIndex++] = [destroyFunction, deps];
})
}
}
实现 useLayoutEffect、useRef
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367#mB9bU
src/react.js
import { useLayoutEffect, useRef } from './react-dom';
export { useLayoutEffect, useRef };
const React = {
useLayoutEffect,
useRef,
}
export default React;
src/react-dom.js
/** useLayoutEffect
*
* 为了保证次回调函数不是同步执行,而是在页面渲染之前执行,可以把它包装成微任务
* @param {*} callback 回调函数
* @param {*} deps 依赖数组
*/
export function useLayoutEffect(callback, deps){
if (hookStates[hookIndex]){
let [destroyFunction, lastDeps] = hookStates[hookIndex];
let same = deps && deps.every((item, index) => item === lastDeps[index]);
if (same){
hookIndex ++;
} else {
destroyFunction && destroyFunction();
queueMicrotask(() =>{
let destroyFunction = callback();
hookStates[hookIndex++] = [destroyFunction, deps];
})
}
} else {
// 用 queueMicrotask 封装成微任务
queueMicrotask(() => {
let destroyFunction = callback(); //执行后,返回销毁函数
hookStates[hookIndex++] = [destroyFunction, deps];
})
}
}
export function useRef(initialState){
hookStates[hookIndex] = hookStates[hookIndex] || {current: initialState};
return hookStates[hookIndex++];
}
实现 forwardRef、useImperativeHandle
demo: https://www.yuque.com/zhuchaoyang/cx9erp/og7367/edit#e38BC
src/react.js
import { forwardRef, useImperativeHandle } from './react-dom';
export { forwardRef, useImperativeHandle };
export function forwardRef(FunctionComponent){
return class Hoc extends Component {
render (){
if (FunctionComponent.length < 2){
console.error('Warning: forwardRef render functions accept exactly two parameters: props and ref. Did you forget to use the ref parameter?');
}
return FunctionComponent(this.props, this.ref);
}
}
}
export function useImperativeHandle(ref, handler){
ref.current = handler();
}
const React = {
forwardRef,
useImperativeHandle,
}
export default React;