xdm,又要到饭了,又更新代码了!
总结一下上一篇完成的内容,
- 描述了虚拟dom的概念以及带来的好处
- 实现了生成虚拟dom的函数
- 虚拟dom转换真实dom的实现
- 函数组件的初步实现
有兴趣的可以点这里查看# 虚拟dom + 实现组件
接下来几篇我们会尝试自己实现几个常用的react hooks的mini版本以加强理解他们的底层实现原理。
为了最大化的考虑广大读者,
-
代码不会使用typescript,
-
代码的实现考虑了所有读者的层次,哪怕你是新手也依然没有什么难度,
-
实现的细节不会包含所有边界情况(mini react的目的让你理解react关键技术实现原理),
1.1 实现 props 和 state 以及 useState
为了使组件具有更强大的功能,我们需要支持 props 和 state。props 是组件的输入,而 state 则用于管理组件内部的状态。useState hook提供了我们组件内部状态的简单管理。
// 平时我们使用useState,一般类似于这样的
function MyComponent() {
const [count, setCount] = useState(0);
}
那么根据函数的签名可以知道,useState 函数接受一个参数返回一个数组,数组的第一个值就是内部状态值,第二个则是更新内部状态值的一个函数。由此我们可先有这样的实现,
let state
function useState(initial) {
state = state || initial
const setState = newState=>{
// 这里会更新组件内部状态值,
state = newState
// 也会更新ui
renderApp()
}
return [state, setState]
}
依据我们现在的进度,你可以理解renderApp会重新运行相关组件以更新ui,至于如何找出哪几个组件需要更新这个会在后续的 diff 算法讲解。这里你可以简单理解renderApp 会更新对应的ui即可。
又基于useState一个组件内部可以多次使用,我们需要创建一个数据结构可以顺序记录每一个useState的内部状态值。所以就有了下面的更新,
//let state
let hooks = []
let currentHook = 0
function useState(initial) {
//state = state || initial
const hookIndex = currentHook
hooks[hookIndex] = hooks[hookIndex] || initial
const setState = newState=>{
// 这里会更新组件内部状态值,
//state = newState
hooks[hookIndex] = newState
// 也会更新ui
renderApp()
}
const state = hooks[hookIndex]
// 这里的index的改变控制了对应的多个usestate 的状态值
currentHook++
return [state, setState]
}
那么一个迷你版本的useState就实现了。
测试
<!DOCTYPE html>
<html>
<head>
<title>Mini React 测试 - useState</title>
<style>
#app-container {
border: 2px solid #000;
padding: 20px;
margin: 20px;
font-size: 18px;
}
button {
padding: 10px 20px;
font-size: 16px;
margin-top: 10px;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
// Mini React 基本实现
function createElement(type, props, ...children) {
return {
type,
props: props || {},
children: children.map(child => typeof child === 'object' ? child : createTextElement(child) ),
};
}
function createTextElement(text) {
return {
type: 'TEXT_ELEMENT',
props: { nodeValue: text },
children: [],
};
}
function render(vdom, container) {
if (typeof vdom.type === 'function') {
const component = vdom.type;
const props = vdom.props;
const childVdom = component(props);
render(childVdom, container);
return;
}
const dom = vdom.type === 'TEXT_ELEMENT'
? document.createTextNode(vdom.props.nodeValue || '')
: document.createElement(vdom.type);
Object.keys(vdom.props).forEach(name => {
if (name.startsWith('on')) {
const eventType = name.slice(2).toLowerCase();
dom.addEventListener(eventType, vdom.props[name]);
} else if (name === 'style') {
Object.assign(dom.style, vdom.props[name]);
} else {
dom[name] = vdom.props[name];
}
});
vdom.children.forEach(child => render(child, dom));
container.appendChild(dom);
}
// useState 实现
let hooks = [];
let currentHook = 0;
function useState(initial) {
const hookIndex = currentHook;
hooks[hookIndex] = hooks[hookIndex] || initial;
const setState = newState => {
hooks[hookIndex] = newState;
renderApp(); // 更新UI
};
const state = hooks[hookIndex];
currentHook++;
return [state, setState];
}
// 定义 renderApp 函数,用于重新渲染应用
function renderApp() {
currentHook = 0; // 重置 currentHook,确保状态按顺序应用
document.getElementById('root').innerHTML = ''; // 清空容器内容
render(createElement(MyStatefulComponent), document.getElementById('root')); // 重新渲染组件
}
// 定义测试组件 MyStatefulComponent
function MyStatefulComponent() {
const [count, setCount] = useState(0);
return createElement(
'div',
{ id: 'app-container' },
`Count: ${count}`,
createElement(
'button',
{ onClick: () => setCount(count + 1) },
'Increment'
)
);
}
// 初始渲染
renderApp();
</script>
</body>
</html>
上述代码中,useState 实现了简单的状态管理。每次组件渲染时,currentHook 确保状态的正确对应。点击按钮时,调用 setCount 更新状态并重新渲染应用。
这里的 renderApp 成功的更新了 ui,但react真正的更新过程需要使用 fiber 以及 reconcile, 即 fiber 结构配合调和算法以及diff 算法 找出需要更新的地方才进行更新。
因为我们还没有实现fiber以及 reconcile的算法以及结构,所以这里暂时使用renderApp 更新了ui。 我们之后的章节有讲解所有你需要知道的 fiber 等如何实现的,等你了解了fiber之后,我们还有一个章节会重构 useState hook。
下一篇,我们将自己实现常用的 useEffect hook,以帮助我们增强对其原理的理解。
如果这样的长度/强度你觉得可以接受,觉得有帮助,可以继续阅读下一篇,实现一个 Mini React:核心功能详解 - useEffect等hook的实现。
如果文章对你有帮助,请点个赞支持一下!
啥也不是,散会。