一篇必看的React文章
本文适合有 React 基础的小伙伴进阶学习
作者:广东靓仔
一、前言
本文基于开源项目:
https://github.com/facebook/react
进行简单的梳理,希望能够让读者一文搞懂React 机制。
1.React中文文档
本文需要对React有所了解,如果暂时没看过React,可以点击下文,学习React基础再来阅读。
传送门: https://react.docschina.org/
二、简单的demo
我们先来看个简单的demo
上面是一个简单的例子,很容易猜到页面会删除“前端早茶”文字,以及一个按钮“点我关注”。
接下来,我们删除 import React from 'react' 删除这行代码,然后仍要得到同样的效果,怎么实现呢?
毫无疑问,我们需要实现自己的React,项目目录结构如下:
+-- src // 开发目录
| +-- gdlz // 存放自己实现react(文件名:广东靓仔)
| | +--index.js // 自己react的逻辑
| +--index.js // 主页面
主页面index.js代码如下:
/*
* @Author: 前端早茶
* @Date: 2021-03-05 21:59:06
* @LastEditTime: 2021-03-06 07:28:44
* @Description: my react
* @FilePath: /reactDemo/src/index.js
*/
import React from './gdlz'
const ReactDOM = React
class Child extends React.Component{
constructor (props) {
super(props)
this.state = {
count: 1
}
}
handleClick = () => {
this.setState({
count: this.state.count + 1
})
}
render () {
return <div>
<h2 onClick={this.handleClick}>{this.state.count}</h2>
</div>
}
}
ChildComponent = React.useComponent(Child)
function App (props){
const [count, setCount] = React.useState(1)
return <div id="container" className="front-end">
<h1>{props.title}, {count}</h1>
<button onClick={() => setCount(count+1)}>关注</button>
<hr/>
<ChildComponent></ChildComponent>
</div>
}
let element = <App title="前端早茶" />
ReactDOM.render(element, document.getElementById('root'))
从上面的代码中,我们可以看到,主页面index里面有:
React.Component
React.useComponent
React.useState
ReactDOM.render
因此我们需要实现:
Component,
useComponent
useState,
render,
需要注意,createElement 是我们的基础,具体原因是因为React使用的是JSX,需要转为React.createElement的。
我截取了React官网的一个demo,大家会更直观
自己的React里面最后要导出内容如下:
三、createElement
/**
* @description: createElement 函数
* @param {*} type 类型
* @param {*} props 参数
* @param {array} children 子元素
* @return {*} 对象
*/
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map(child =>
typeof child === "object"
? child
: createTextElement(child)
),
},
}
/**
* @description: createTextElement 函数
* @param {*} text 文本,用于直接渲染文字
* @return {*} 对象
*/
function createTextElement(text) {
return {
type: "TEXT",
props: {
nodeValue: text,
children: [],
},
}
}
四、Component、useComponent
/**
* @description: Component 组件函数
* @param {*}
* @return {*}
*/
class Component {
constructor(props){
this.props = props
}
}
/**
* @description: useComponent
* @param {*} Component 传入的class组件
* @return {*}
*/
function useComponent(Component){
return function(props){
const component = new Component(props)
let [state, setState] = useState(component.state)
component.props = props
component.state = state
component.setState = setState
console.log(component)
return component.render()
}
}
五、useState
// 下一个单元任务
let nextUnitOfWork = null
// 工作中的fiber
let wipRoot = null
let currentRoot = null
let deletions = null
let wipFiber = null
let hookIndex = null
/**
* @description: useState 函数
* @param {*} init component.state
* @return {*}
*/
function useState(init){
const oldHook =
wipFiber.base &&
wipFiber.base.hooks &&
wipFiber.base.hooks[hookIndex]
const hook = {
state: oldHook ? oldHook.state : init,
queue: [],
}
const actions = oldHook ? oldHook.queue : []
actions.forEach(action => {
hook.state = action
})
const setState = action => {
hook.queue.push(action)
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
base: currentRoot,
}
nextUnitOfWork = wipRoot
deletions = []
}
wipFiber.hooks.push(hook)
hookIndex++
return [hook.state, setState]
}
六、render
// 设置全局 下一个任务单元
let nextUnitOfWork = null
/**
* @description: render函数
* @param {*} vdom 虚拟dom
* @param {*} container 容器
* @return {*}
*/
function render(vdom, container){
// 工作中的fiber
wipRoot = {
dom: container,
props: {
children: [vdom],
},
base: currentRoot,
}
deletions = []
nextUnitOfWork = wipRoot
}
从这个render可以看到,我们里面有使用到下一个任务单元,很明显会涉及到协调调度children的逻辑。由于篇幅关系这里简单描述一下:
任务调度reconcileChildren
假如当前有任务,而且当前帧还没结束,我们获取下一个任务单元,并且记录起来。当没有下一个任务了,咋们提交root。在这当中我们会使用到window.requestIdleCallback()这个函数。其作用是在浏览器的空闲时段内调用的函数排队,函数一般会按先进先调用的顺序执行。关于这个函数的具体介绍,我们可以在MDN进行阅读。
这里稍微提一下,React17如果子节点是数组,会调用reconcileChildrenArray。
七、总结
我们实现了一个自己的React,很明显我们有很多细节没有考虑进去。不过经过梳理我们现在对React有了一个清晰的认识,阅读源码的时候我们分块进行理解,化整为零~
关注前端早茶,我们一起学习,共同进步