创建运行环境
pnpm create vite
- 选择Vanilla创建项目 选择javascript就行
- 删除多余文件 保留最简单目录
实现最简单mini-react
渲染dom
- index.html
<div id="root"></div>
<script type="module" src="./main.js"></script>
- main.js代码
const dom = document.createElement("div");
dom.id="app"
document.querySelector("#root").appendChild(dom);
const text = document.createTextNode("");
text.nodeValue = "hello mini react";
dom.append(text)
这样就可以在浏览器上看到hello mini react了
封装创建虚拟dom节点
- 首先抽离节点
const textNode = {
type: "TEXT_ELEMENT",
props: {
nodeValue: "hello mini react",
children: []
}
}
const el = {
type: "div",
props: {
id: "app",
children: [textNode]
}
}
- 渲染dom
const dom = document.createElement(el.type);
dom.id=el.props.id
document.querySelector("#root").appendChild(dom);
const text = document.createTextNode("");
text.nodeValue = textNode.props.nodeValue;
dom.append(text)
可以看到结果是一样的
封装函数
- 把上面的el和textNode封装一下方便调用
function createTextNode(nodeValue) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children,
},
};
}
- 渲染dom
const textNode=createTextNode("hello mini react");
const el = createElement("div",{id:"app"},textNode)
const dom = document.createElement(el.type);
dom.id=el.props.id
document.querySelector("#root").appendChild(dom);
const text = document.createTextNode("");
text.nodeValue = textNode.props.nodeValue;
dom.append(text)
可以看到结果是一样的
封装render函数
- 创建一个DOM节点,根据el的类型来决定是创建一个文本节点还是一个元素节点
- 遍历el的props属性,将除了children之外的属性都赋值给dom节点
- 获取el的children属性 遍历children,对每个子元素调用render函数进行递归渲染
- 把子节点添加到父节点中
function render(el, container) {
// 创建一个DOM节点,根据el的类型来决定是创建一个文本节点还是一个元素节点
const dom =
el.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(el.type);
// 遍历el的props属性,将除了children之外的属性都赋值给dom节点
Object.keys(el.props).forEach(key => {
if (key !== "children") {
dom[key] = el.props[key];
}
})
// 获取el的children属性
const children = el.props.children
// 遍历children,对每个子元素调用render函数进行递归渲染
children.forEach(child => {
render(child, dom)
})
// 将dom添加到container中
container.append(dom);
}
- 重构createElement函数 之前我们传递节点是createTextNode(“hello mini react”) 现在想之间写"hello mini react" 需要修改函数
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === "string" ? createTextNode(child) : child
}),
},
};
}
- 渲染dom
const el = createElement("div",{id:"app"},"hello mini react")
render(el,document.querySelector("#root"))
对齐react 调用方式
- 创建core 文件夹 里面包含React.js和ReactDOM.js两个文件
- 创建src文件夹 里面包含App.js文件
- React.js
function createTextNode(nodeValue) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue,
children: [],
},
};
}
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child => {
return typeof child === "string" ? createTextNode(child) : child
}),
},
};
}
function render(el, container) {
const dom =
el.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(el.type);
Object.keys(el.props).forEach(key => {
if (key !== "children") {
dom[key] = el.props[key];
}
})
const children = el.props.children
children.forEach(child => {
render(child, dom)
})
container.append(dom);
}
const React={
render,
createElement
}
export default React
- ReactDOM.js
import React from './React.js'
const ReactDOM = {
createRoot(container) {
return {
render(App){
React.render(App, container)
}
}
}
}
export default ReactDOM
- App.js
import React from '../core/React.js'
const App =<div>Hello mini react! <span>Hi React</span></div>
export default App
- main.js
import App from './src/App.js'
import ReactDOM from './core/ReactDOM.js'
ReactDOM.createRoot(document.querySelector("#root")).render(App)
运行项目发现效果是一样的
使用 jsx
因为刚开始使用vite创建的项目 所以把App.js和main.js改成App.jsx和main.jsx 然后在index.hrml script引用 在运行项目即可
以上就是我们对mini-react的基本搭建
任务调度器&fiber架构
使用了 requestIdleCallback
为什么使用requestIdleCallback
因为 render 函数中执行大量dom 渲染的时候 会导致卡顿,我们需要对任务进行拆分,拆分成一个个小任务,然后依次执行,从而避免卡顿
封装一个workLoop方法
- React.js
// 工作循环函数
let nextWorkOfUnit = {};
function workLoop(deadline) {
// 工作循环函数,用于不断执行任务直至满足条件
let shouldDeadline = false; // 初始化一个变量用于判断是否需要满足截止时间
while (!shouldDeadline && nextWorkOfUnit) {
// 循环执行任务,直到满足截止时间条件或者没有任务可执行
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit); // 使用任务处理函数处理当前任务对象
shouldDeadline = deadline.timeRemaining() < 1; // 判断当前任务的截止时间是否小于1
}
// 请求下一次执行该函数的时间间隔,并递归调用该函数
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
- 实现 filber 架构(把树结构转变成链表结构)
- 首现判断当前子节点(child)中有没有子节点(child)
- 如果当前节点没有子节点,就找当前节点的兄弟节点(sibling)
- 如果当前节点没有兄弟节点(sibling),就找当前节点的父节点(parent) 的兄弟节点(sibling)
- 如果当前节点的父节点(parent) 没有兄弟节点(sibling),就在往上找
- 如果当前节点没有 parent 那么就结束
- 实现performWorkOfUnit
function createDom(type) {
return type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(type);
}
function updateProps(dom,props){
Object.keys(props).forEach((key) => {
if (key !== "children") {
dom[key] = props[key];
}
})
}
function initChildren(fiber){
const children = fiber.props.children;
let prvChild = null;
children.forEach((child, index) => {
const newFiber = {
type: child.type,
props: child.props,
parent: fiber,
child: null,
sibling: null,
dom: null,
};
if (index === 0) {
fiber.child = newFiber;
} else {
prvChild.sibling = newFiber;
}
prvChild = newFiber;
});
}
function performWorkOfUnit(fiber) {
if (!fiber.dom) {
const dom = (fiber.dom =createDom(fiber.type));
fiber.parent.dom.append(dom);
updateProps(dom,fiber.props)
}
initChildren(fiber)
if (fiber.child) {
return fiber.child;
}
if (fiber.sibling) {
return fiber.sibling;
}
return fiber.parent?.sibling;
}
- 修改render
function render(el, container) {
nextWorkOfUnit = {
dom: container,
props: {
children: [el],
},
};
}
统一提交&实现 function component
统一提交
我们使用 requestIdleCallback实现任务调度,但是它只有等待浏览器有空闲时间才会执行任务,如果任务很多,那页面渲染就只能看到一半渲染。
- React.js
let nextWorkOfUnit = {}
let root = null
function render(el, container) {
nextWorkOfUnit = {
dom: container,
props: {
children: [el],
},
};
root=nextWorkOfUnit
}
function workLoop(deadline) {
let shouldDeadline = false;
while (!shouldDeadline && nextWorkOfUnit) {
nextWorkOfUnit = sunWorkFun(nextWorkOfUnit);
shouldDeadline = deadline.timeRemaining() < 1;
}
if(!nextWorkOfUnit&&root){
commitRoot()
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function commitRoot(){
commitWork(root.child)
}
function commitWork(fiber){
if(!fiber) return;
if(fiber.dom){
fiber.parent.dom.append(fiber.dom);
}
commitWork(fiber.child)
commitWork(fiber.sibling)
}
实现支持 function component
现在我们渲染dom是这样
ReactDOM.createRoot(document.querySelector("#root")).render(App)
我们想改成ReactDOM.createRoot(document.querySelector("#root")).render(<App />)
- React.js
// 创建元素节点
/**
* 创建一个元素
* @param {string} type - 元素的类型
* @param {Object} props - 元素的属性
* @param {...any} children - 元素的子元素
* @returns {Object} - 创建的元素对象
*/
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) => {
/**
* 判断子元素是否为文本节点
* @type {boolean}
*/
const isTextNode =
typeof child === "string" || typeof child === "number";
return isTextNode ? createTextNode(child) : child;
}),
},
};
}
// 提交节点
function commitWork(fiber) {
// 检查fiber是否存在
if (!fiber) return;
// 初始化fiber的父级节点
let fiberParent = fiber.parent;
// 循环找到有dom节点的父级节点
while (!fiberParent.dom) {
fiberParent = fiberParent.parent;
}
fiberParent.dom.append(fiber.dom);
// 递归调用commitWork函数处理fiber的子节点
commitWork(fiber.child);
// 递归调用commitWork函数处理fiber的兄弟节点
commitWork(fiber.sibling);
}
/**
* 更新函数组件
*
* @param {Object} fiber - Fiber对象
*/
function updateFunctionComponent(fiber) {
const children = [fiber.type(fiber.props)];
initChildren(fiber, children);
}
function updateHostComponent(fiber) {
// 如果fiber没有关联的dom节点
if (!fiber.dom) {
// 创建一个新的dom节点
const dom = (fiber.dom = createDom(fiber.type));
// 更新dom节点的属性
updateProps(dom, fiber.props, {});
}
// 获取子元素
const children = fiber.props.children;
// 初始化子元素
initChildren(fiber, children);
}
/**
* 函数:performWorkOfUnit
* 描述:用于渲染节点的函数
* 参数:
* - fiber:fiber对象,包含节点的信息
*/
function performWorkOfUnit(fiber) {
/**
* 变量:isFunctionComponent
* 类型:boolean
* 描述:判断fiber.type是否为函数节点
*/
const isFunctionComponent = typeof fiber.type === "function";
/**
* 判断不是函数节点且fiber.dom不存在时,创建dom节点并更新属性
*/
if (isFunctionComponent) {
updateFunctionComponent(fiber);
} else {
updateHostComponent(fiber);
}
/**
* 判断fiber是否有子节点,返回子节点
*/
if (fiber.child) {
return fiber.child;
}
/**
* 变量:nextFiber
* 类型:fiber对象
* 描述:遍历fiber对象的父级节点
*/
let nextFiber = fiber;
while (nextFiber) {
/**
* 判断nextFiber是否有兄弟节点,返回兄弟节点
*/
if (nextFiber.sibling) return nextFiber.sibling;
nextFiber = nextFiber.parent;
}
}
进军 vdom 的更新
实现绑定事件
- App.jsx
function App() {
function handleClick(){
console.log("🚀 ~ App ~ App:")
}
return (
<div>
<button onClick={handleClick}>click</button>
</div>
);
}
- 修改updateProps函数
function updateProps(dom,props){
Object.keys(props).forEach(key=>{
if(key.startsWith('on')){
// 事件名
const eventType = key.slice(2).toLowerCase();// 或.substring(2);
dom.addEventListener(eventType,props[key]);
}else{
dom[key] = props[key];
}
})
}
更新props
对比 new vdom tree VS old vdom tree,找出差异,更新dom
- 创建 update 函数 使用currentRoot变量来存放当前的根节点
let currentRoot=null;
/**
* 递归地提交根节点的子节点工作
*/
function commitRoot() {
commitWork(root.child);
currentRoot = root
root = null
}
function update() {
// 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
root = {
dom: currentRoot.dom,
props: currentRoot.props
};
root=nextWorkOfUnit
}
- 找到老的节点
function update() {
// 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
root = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot //老的节点
};
root=nextWorkOfUnit
}
/**
* 初始化子fiber对象
* @param {Object} fiber - 当前fiber对象
* @param {Array} children - 子子fiber对象数组
*/
function initChildren(fiber, children) {
let prvChild = null; // 上一个子fiber对象
let oldFiber = fiber.alternate?.child; // 备用fiber对象
children.forEach((child, index) => { // 遍历子fiber对象数组
const isSameType = oldFiber && oldFiber.type === child.type; // 判断是否为相同类型
let newFiber = null; // 创建新的fiber对象
if (isSameType) { // 如果是相同类型
newFiber = {
type: child.type, // 设置fiber类型为子fiber类型
props: child.props, // 设置fiber属性为子fiber属性
parent: fiber, // 设置fiber父fiber为当前fiber
child: null, // 设置fiber子fiber为null
sibling: null, // 设置fiber兄弟fiber为null
dom: oldFiber.dom, // 设置fiber的dom为备选fiber的dom
effectTag: "update", // 设置fiber效果标签为"update"
alternate: oldFiber // 设置fiber备选fiber为备选fiber
};
} else { // 如果不是相同类型
newFiber = {
type: child.type, // 设置fiber类型为子fiber类型
props: child.props, // 设置fiber属性为子fiber属性
parent: fiber, // 设置fiber父fiber为当前fiber
child: null, // 设置fiber子fiber为null
sibling: null, // 设置fiber兄弟fiber为null
dom: null, // 设置fiber的dom为null
effectTag: "placement" // 设置fiber效果标签为"placement"
};
}
if (oldFiber) { // 如果备选fiber存在
oldFiber = oldFiber.sibling; // 将备选fiber指向下一个fiber
}
if (index === 0) { // 如果是第一个子fiber
fiber.child = newFiber; // 设置当前fiber的子fiber为新fiber
} else {
prvChild.sibling = newFiber; // 设置前一个子fiber的兄弟fiber为新fiber
}
prvChild = newFiber; // 更新前一个子fiber为新fiber
});
}
- 比较diff props
- old 有 new 没有 删除
- new 有 old 没有 添加
- old new 都有 更新 2和3 可以合并成一个处理
// 提交节点
function commitWork(fiber) {
// 检查fiber是否存在
if (!fiber) return;
// 初始化fiber的父级节点
let fiberParent = fiber.parent;
// 循环找到有dom节点的父级节点
while (!fiberParent.dom) {
fiberParent = fiberParent.parent;
}
// 如果fiber的effectTag为'update',则更新fiber的dom节点的属性
if (fiber.effectTag === 'update') {
updateProps(fiber.dom, fiber.props, fiber.alternate?.props)
}
// 如果fiber的effectTag为'placement',则将fiber的dom节点添加到fiber的父级节点中
else if (fiber.effectTag === 'placement') {
if (fiber.dom) {
fiberParent.dom.append(fiber.dom);
}
}
// 递归调用commitWork函数处理fiber的子节点
commitWork(fiber.child);
// 递归调用commitWork函数处理fiber的兄弟节点
commitWork(fiber.sibling);
}
/**
* 更新属性
* @param {Object} dom - DOM元素
* @param {Object} nextProps - 新的属性
* @param {Object} prvProps - 旧的属性
*/
function updateProps(dom, nextProps, prvProps) {
Object.keys(prvProps).forEach((key) => {
if (key !== "children") { // 排除children属性
if (!(key in nextProps)) { // 如果新属性中不存在该键
dom.removeAttribute(key); // 移除该属性
}
}
});
Object.keys(nextProps).forEach((key) => {
if (key !== "children") { // 排除children属性
if (nextProps[key] !== prvProps[key]) { // 如果新旧属性值不相同
if (key.startsWith("on")) { // 如果属性名以"on"开头
const eventType = key.slice(2).toLowerCase() // 获取事件类型
dom.removeEventListener(eventType, prvProps[key]) // 移除事件监听器
dom.addEventListener(eventType, nextProps[key]) // 添加事件监听器
} else {
dom[key] = nextProps[key]; // 更新属性值
}
}
}
});
}
function updateHostComponent(fiber) {
// 如果fiber没有关联的dom节点
if (!fiber.dom) {
// 创建一个新的dom节点
const dom = (fiber.dom = createDom(fiber.type));
// 更新dom节点的属性
updateProps(dom, fiber.props, {});
}
// 获取子元素
const children = fiber.props.children;
// 初始化子元素
initChildren(fiber, children);
}
- 变量重命名 为了跟react保持一致性 root改为wipRoot initChildren改为reconcileChildren
- 更新以下代码
function render(el, container) {
// 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
wipRoot = {
dom: container,
props: {
children: [el],
},
};
nextWorkOfUnit = wipRoot
}
function update() {
// 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
};
nextWorkOfUnit = wipRoot
}
- 导出update
- 查看效果 App.jsx
import React from "../core/React.js";
let count = 10;
let props = {id:'container'}
function Counter({num}) {
function handleClick(){
count++;
props = {}
React.update()
}
return <div {...props}>Hi React <button onClick={handleClick}>count:{count}</button></div>;
}
function App(){
return <div>
Hello mini react!
<Counter num={10} />
</div>
}
export default App;
击杀 update children
- 创建和删除(type不一致的时候 删除旧的 创建新的)
let deletions = [];
let wipFiber = null;
/**
* 递归地提交根节点的子节点工作
*/
function commitRoot() {
deletions.forEach(commitDeletions);
commitWork(wipRoot.child);
currentRoot = wipRoot;
wipRoot = null;
deletions = [];
}
function commitDeletions(fiber) {
if (fiber.dom) {
// 初始化fiber的父级节点
let fiberParent = fiber.parent;
// 循环找到有dom节点的父级节点
while (!fiberParent.dom) {
fiberParent = fiberParent.parent;
}
fiberParent.dom.removeChild(fiber.dom);
} else {
commitDeletions(fiber.child);
}
}
/**
* 初始化子fiber对象
* @param {Object} fiber - 当前fiber对象
* @param {Array} children - 子子fiber对象数组
*/
function reconcileChildren(fiber, children) {
let prvChild = null; // 上一个子fiber对象
let oldFiber = fiber.alternate?.child; // 备用fiber对象
children.forEach((child, index) => {
// 遍历子fiber对象数组
const isSameType = oldFiber && oldFiber.type === child.type; // 判断是否为相同类型
let newFiber = null; // 创建新的fiber对象
if (isSameType) {
// 如果是相同类型
newFiber = {
type: child.type, // 设置fiber类型为子fiber类型
props: child.props, // 设置fiber属性为子fiber属性
parent: fiber, // 设置fiber父fiber为当前fiber
child: null, // 设置fiber子fiber为null
sibling: null, // 设置fiber兄弟fiber为null
dom: oldFiber.dom, // 设置fiber的dom为备选fiber的dom
effectTag: "update", // 设置fiber效果标签为"update"
alternate: oldFiber, // 设置fiber备选fiber为备选fiber
};
} else {
if (child) {
// 如果不是相同类型
newFiber = {
type: child.type, // 设置fiber类型为子fiber类型
props: child.props, // 设置fiber属性为子fiber属性
parent: fiber, // 设置fiber父fiber为当前fiber
child: null, // 设置fiber子fiber为null
sibling: null, // 设置fiber兄弟fiber为null
dom: null, // 设置fiber的dom为null
effectTag: "placement", // 设置fiber效果标签为"placement"
};
}
if (oldFiber) {
deletions.push(oldFiber); // 将备选fiber添加到删除数组
}
}
if (oldFiber) {
// 如果备选fiber存在
oldFiber = oldFiber.sibling; // 将备选fiber指向下一个fiber
}
if (index === 0) {
// 如果是第一个子fiber
fiber.child = newFiber; // 设置当前fiber的子fiber为新fiber
} else {
prvChild.sibling = newFiber; // 设置前一个子fiber的兄弟fiber为新fiber
}
if (newFiber) {
prvChild = newFiber; // 更新前一个子fiber为新fiber
}
});
while (oldFiber) {
deletions.push(oldFiber); // 将备选fiber添加到删除数组
oldFiber = oldFiber.sibling;
}
}
// 工作循环函数
function workLoop(deadline) {
// 工作循环函数,用于不断执行任务直至满足条件
let shouldDeadline = false; // 初始化一个变量用于判断是否需要满足截止时间
while (!shouldDeadline && nextWorkOfUnit) {
// 循环执行任务,直到满足截止时间条件或者没有任务可执行
nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit); // 使用任务处理函数处理当前任务对象
if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
nextWorkOfUnit = undefined;
}
shouldDeadline = deadline.timeRemaining() < 1; // 判断当前任务的截止时间是否小于1
}
if (!nextWorkOfUnit && wipRoot) {
// 如果没有任务可执行且存在根对象,则提交根对象
commitRoot();
}
// 请求下一次执行该函数的时间间隔,并递归调用该函数
requestIdleCallback(workLoop);
}
/**
* 更新函数组件
*
* @param {Object} fiber - Fiber对象
*/
function updateFunctionComponent(fiber) {
wipFiber = fiber;
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
function update() {
const currentFiber = wipFiber;
return () => {
// 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
wipRoot = {
...currentFiber,
alternate: currentFiber,
};
nextWorkOfUnit = wipRoot;
};
}
- App.jsx
function Bar() {
const update = React.update()
function handleClick(){
countBar++;
update()
}
return <div>
<h1>Foo</h1>
{countBar}
<button onClick={handleClick}>click</button>
</div>;
}
搞定 useState
- 首先经常见到 react更新状态的时候使用useState
- App.jsx
function Foo() {
const [count,setCount] = React.useState(1)
function handleClick(){
setCount((c)=>c+1)
}
return <div>
<h1>Foo</h1>
{count}
<button onClick={handleClick}>click</button>
</div>;
}
- main.js
/**
* useState(initialState)函数用于在当前fiber中创建一个新的stateHook。
* @param {any} initialState - 初始状态值
* @returns {Array} - 返回包含当前状态和setState方法的数组
*/
function useState(initialState) {
const currentFiber = wipFiber;
const oldHooks = currentFiber.alternate?.stateHook;
const stateHook = {
state: oldHooks ? oldHooks.state : initialState
};
currentFiber.stateHook = stateHook;
/**
* setState(actions)函数用于更新stateHook.state。
* @param {Function|Object} actions - 更新状态的函数或要更新的状态对象
*/
function setState(actions) {
stateHook.state = actions(stateHook.state)
wipRoot = {
...currentFiber,
alternate: currentFiber,
};
nextWorkOfUnit = wipRoot;
}
return [stateHook.state, setState];
}
这样我们在使用上面dom的时候就可以正常使用了 但是还有问题 就是现在只有一个hook 假如有多个的话最后一个执行结果就会替换前面的 所有还需要把之前的存起来 并且批量执行函数
let stateHooks=[];
let stateHookIndex=0;
/**
* 更新函数组件
*
* @param {Object} fiber - Fiber对象
*/
function updateFunctionComponent(fiber) {
stateHooks = [];
stateHookIndex = 0;
wipFiber = fiber;
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
/**
* useState(initialState)函数用于在当前fiber中创建一个新的stateHook。
* @param {any} initialState - 初始状态值
* @returns {Array} - 返回包含当前状态和setState方法的数组
*/
function useState(initialState) {
const currentFiber = wipFiber;
const oldHooks = currentFiber.alternate?.stateHooks[stateHookIndex];
const stateHook = {
state: oldHooks ? oldHooks.state : initialState,
queue: oldHooks ? oldHooks.queue : [],
};
// 执行stateHook.queue中的所有action,更新stateHook.state
stateHook.queue.forEach((action) => {
stateHook.state = action(stateHook.state);
});
stateHook.queue = [];
stateHookIndex++;
stateHooks.push(stateHook);
currentFiber.stateHooks = stateHooks;
/**
* setState(actions)函数用于更新stateHook.state。
* @param {Function|Object} actions - 更新状态的函数或要更新的状态对象
*/
function setState(actions) {
// 将action添加到stateHook.queue中
stateHook.queue.push(actions);
wipRoot = {
...currentFiber,
alternate: currentFiber,
};
nextWorkOfUnit = wipRoot;
}
return [stateHook.state, setState];
}
这样的话就解决了上面的问题 但是假如说我们调用
setCount(11)
还是有问题 因为我们存入的不是function 所有还需要修改useState
function useState(initialState) {
// 省略...
function setState(actions) {
const isFunction = typeof actions === "function";
// 将action添加到stateHook.queue中
stateHook.queue.push(isFunction ? actions : () => actions);
// 省略...
}
return [stateHook.state, setState];
}
- 提前检测 减少不必要的更新 假如说当前值是11,setCount的还是11 这样情况就没必要更新
function useState(initialState) {
// 省略...
function setState(actions) {
const isFunction = typeof actions === "function";
const eagerState = isFunction ? actions(stateHook.state) : actions;
if (eagerState === stateHook.state) return;
// 将action添加到stateHook.queue中
stateHook.queue.push(isFunction ? actions : () => actions);
// 省略...
}
return [stateHook.state, setState];
}
搞定 useEffect
- useEffect 调用的时机是在 React 完成对 DOM 的渲染之后,并且浏览器完成绘制之前
- useEffect 的第二个参数是依赖数组,不指定的时候副作用指挥在组件渲染后执行一次,如果指定了依赖数组,那么只有当依赖数组中的值发生变化时,副作用才会执行
- App.jsx
function Foo() {
console.log("🚀 ~ Foo ~ Foo:")
const [count,setCount] = React.useState(1)
function handleClick(){
setCount((c)=>c+1)
}
React.useEffect(()=>{
console.log('useEffect: ');
},[])
return <div>
<h1>Foo</h1>
{count}
<button onClick={handleClick}>click</button>
</div>;
}
- React.js
/**
* 递归地提交根节点的子节点工作
*/
function commitRoot() {
deletions.forEach(commitDeletions);
commitWork(wipRoot.child);
commitEffectHooks();
currentRoot = wipRoot;
wipRoot = null;
deletions = [];
}
function useEffect(callback, deps) {
// 创建一个effectHook对象,包含callback、deps和cleanup属性
const effectHook = {
callback,
deps,
cleanup: undefined,
};
// 将effectHooks赋值给wipFiber的effectHooks属性
wipFiber.effectHook = effectHook;
}
function commitEffectHooks() {
// 开始执行effectHooks的回调函数
function run(fiber) {
if (!fiber) return;
fiber.effectHook?.callback()
// 递归执行run函数
run(fiber.child);
run(fiber.sibling);
}
// 开始执行effectHooks的回调函数
run(wipRoot);
}
- 上面函数初始化的时候就可以监听到了
- 更新监听
function commitEffectHooks() {
// 开始执行effectHooks的回调函数
function run(fiber) {
if (!fiber) return;
if (!fiber.alternate) {
fiber.effectHook?.callback()
} else {
const oldHooks = fiber.alternate?.effectHook;
const needUpdate = oldHooks.deps.some((olDep, idx) => {
return olDep !== fiber.effectHook.deps[idx];
});
needUpdate && fiber.effectHook.callback();
}
// 递归执行run函数
run(fiber.child);
run(fiber.sibling);
}
run(wipRoot);
}
更新已经实现 但是还有问题 他现在只能监听一个 所有监听多个需要修改代码
/**
* 更新函数组件
*
* @param {Object} fiber - Fiber对象
*/
function updateFunctionComponent(fiber) {
stateHooks = [];
stateHookIndex = 0;
effectHooks = [];
wipFiber = fiber;
const children = [fiber.type(fiber.props)];
reconcileChildren(fiber, children);
}
let effectHooks = [];
function useEffect(callback, deps) {
// 创建一个effectHook对象,包含callback、deps和cleanup属性
const effectHook = {
callback,
deps
};
// 将effectHook添加到effectHooks数组中
effectHooks.push(effectHook);
// 将effectHooks赋值给wipFiber的effectHooks属性
wipFiber.effectHooks = effectHooks;
}
function commitEffectHooks() {
// 开始执行effectHooks的回调函数
function run(fiber) {
if (!fiber) return;
if (!fiber.alternate) {
// 初始化effectHooks的cleanup方法
fiber.effectHooks?.forEach((hook) => hook.callback());
} else {
// 更新effectHooks的cleanup方法
fiber.effectHooks?.forEach((newHooks, index) => {
if (newHooks.deps.length > 0) {
const oldHooks = fiber.alternate?.effectHooks[index];
const needUpdate = oldHooks.deps.some((olDep, idx) => {
return olDep !== newHooks.deps[idx];
});
needUpdate && newHooks.callback();
}
});
}
// 递归执行run函数
run(fiber.child);
run(fiber.sibling);
}
// 开始执行effectHooks的回调函数
run(wipRoot);
}
- cleanup 实现
- cleanup在调用useEffect之前调用 当deps为空的时候不会返回cleanup
- 作用就是清空副作用
- App.jsx 表现形式
function Foo() {
console.log("🚀 ~ Foo ~ Foo:")
const [count,setCount] = React.useState(1)
function handleClick(){
setCount((c)=>c+1)
setBar('bar11')
}
React.useEffect(()=>{
console.log('useEffect: ');
return ()=>{
console.log('return-cleanup: ');
}
},[])
React.useEffect(()=>{
console.log('count: ',count);
return ()=>{
console.log('return-cleanup: ');
}
},[count])
return <div>
<h1>Foo</h1>
{count}
<button onClick={handleClick}>click</button>
</div>;
}
- React.js
function useEffect(callback, deps) {
// 创建一个effectHook对象,包含callback、deps和cleanup属性
const effectHook = {
callback,
deps,
cleanup: undefined,
};
// 将effectHook添加到effectHooks数组中
effectHooks.push(effectHook);
// 将effectHooks赋值给wipFiber的effectHooks属性
wipFiber.effectHooks = effectHooks;
}
function commitEffectHooks() {
// 开始执行effectHooks的回调函数
function run(fiber) {
if (!fiber) return;
if (!fiber.alternate) {
// 初始化effectHooks的cleanup方法
fiber.effectHooks?.forEach((hook) => {
hook.cleanup = hook.callback();
});
} else {
// 更新effectHooks的cleanup方法
fiber.effectHooks?.forEach((newHooks, index) => {
if (newHooks.deps.length > 0) {
const oldHooks = fiber.alternate?.effectHooks[index];
const needUpdate = oldHooks.deps.some((olDep, idx) => {
return olDep !== newHooks.deps[idx];
});
needUpdate && (newHooks.cleanup = newHooks.callback());
}
});
}
// 递归执行run函数
run(fiber.child);
run(fiber.sibling);
}
// 执行effectHooks的cleanup方法
function runCleanup(fiber){
// 如果fiber为空,直接返回
if (!fiber) return;
// 如果fiber的alternate属性存在,且effectHooks属性存在
fiber.alternate?.effectHooks?.forEach(hook=>{
// 如果hook的deps数组长度大于0
if (hook.deps.length > 0) {
// 如果hook有cleanup方法,则执行cleanup方法
hook.cleanup && hook.cleanup();
}
})
// 递归执行runCleanup方法,传入fiber的child属性作为参数
runCleanup(fiber.child);
// 递归执行runCleanup方法,传入fiber的sibling属性作为参数
runCleanup(fiber.sibling);
}
// 开始执行effectHooks的回调函数
runCleanup(wipRoot);
run(wipRoot);
}
到此我们最简单的mini-react 就完成了
文中仅是自己在学习过程的总结,很多地方可能文笔有误,个人思考能力也不足。仅作为个人的学习结课文章,谢谢大家。文章来源:https://www.toymoban.com/news/detail-814351.html
github文章来源地址https://www.toymoban.com/news/detail-814351.html
到了这里,关于实现自己的mini-react的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!