使用ts手写react,实现其API
仓库地址:https://github.com/xuxiaozhou/my-react
食用方法
git clone https://github.com/xuxiaozhou/my-react
yarn install
yarn build
# 测试代码目录:./demo
已实现的React全家桶功能
- hook
- lifecycle
- state,props
- event
- Component,PureComponent,FunctionComponent
- react-router
- react-router-dom
- history
- redux
实现细节
挂载流程
- ReactDOM.render(vdom,container)
- 通过vdom进行挂载 mount(vdom,container)
- 通过vdom创建真实dom (createDOM)
- 类组件
- new出实例 -> 生命周期 constructor
- 调用getDerivedStateFromProps获取最新的部分state, 设置到实例上
- 调用render方法获取renderVdom(要渲染的虚拟dom) -> 生命周期 render
- 将 renderVdom 递归调用 createDOM
- 函数组件
- 调用函数组件获取renderVdom(要渲染的虚拟dom)
- 将 renderVdom 递归调用createDOM
- HTML元素
- 创建真实dom
- 处理props
- 递归children
- 数组遍历调用 mount
- 对象直接调用 mount
- 将真实dom插入container容器 -> 生命周期:componentDidMount
- 类组件
事件机制
在react函数处理事件,生命周期处理函数都是setState都是批量更新模式,等到函数同步执行完毕够一次性更新
- 将事件处理函数(listener)绑定到真实dom身上,后续通过event.target获取
- 将事件绑定到document身上,统一用dispatchEvent作为所有事件的处理函数
- dispatchEvent
- 进入批量更新处理模式: updateQueue.isBatchingUpdate = true
- 创建合成事件
- 通过event.target向上递归,获取当前事件名的绑定事件
- 执行批量更新 updateQueue.batchUpdate()
setState
前置
- 维护一个全局updateQueue更新队列
- 类组件执行初始化时,会初始化一个updater
真实流程
- updater.addState
- 判断当前的的更新模式
- 批量更新
- 将当前updater添加到 updateQueue 的updaters即可
- 立即执行
- 执行updater.updateClassComponent
- 批量更新
- 批量更新模式:
- 事件函数处理完成后:updateQueue.batchUpdate
- 遍历所有的updateQueue.updaters
- 执行updater.updateClassComponent
- 执行完清空updaters
- 重制为立即更新模式
更新模式
- 执行getDerivedStateFromProps,获取新的部分state
- 设置最新的state和props
- 执行shouldComponentUpdate
- 返回false
- 更新结束
- 返回true
- 执行render获取最新的renderVdom
- 执行getSnapshotBeforeUpdate
- 新旧renderVdom进行dom-diff
- 执行componentDidUpdate
- 返回false
调用类组件的forceUpdate
forceUpdate的更新流程不走shouldComponentUpdate,直接走更新流程
- 执行getDerivedStateFromProps获取最新的部分state
- 设置最新的state和props
- 执行render获取最新renderVdom
- 执行getSnapshotBeforeUpdate
- 新旧renderVdom进行dom-diff
- 执行componentDidUpdate
卸载阶段
- componentWillUnmount
PureComponent和Component
实现shouldComponentUpdate,对新旧props和新旧state进行浅层比较。 如果相同则返回true,不走更新流程 如果不同则返回false,走更新流程
export abstract class PureComponent extends Component {
// @ts-ignore
shouldComponentUpdate(nextProps, nextState) {
// true -> 不更新
// false -> 更新
return shallowEqual(this.props, nextProps) && shallowEqual(this.state, nextState);
}
}
React.createContext
将 上下文数据 挂载到Provider函数的静态属性_value上
function createContext() {
// 提供, 是函数组件
function Provider(props) {
// 保持_value的对象指针
if (!Provider._value) {
Provider._value = props.value || {};
} else {
Object.assign(Provider._value, props.value);
}
return props.children;
}
function Consumer(props) {
props.children(Provider._value);
}
return { Provider, Consumer };
}
在类组件上使用 static contextType = xxx
在new 类组件 时候如果类组件有contextType静态方法则进行赋值。由于是Provider._value不会修改引用地址,则后续拿到的都是最新的值
// react-dom/src/index mountClassComponent
// 进行赋值
instance.context = Type.contextType.Provider._value
React.memo
使用React.PureComponent进行包装,在shouldComponentUpdate对新旧props进行比较
function memo(FunctionComponent) {
return class ClassComponent extends PureComponent {
render() {
return FunctionComponent(this.props);
}
};
}
React.createRef
function createRef() {
return { current: null };
}
赋值阶段
如果ref绑定到DOM元素
// react-dom/src/index.ts
// 挂载的时候进入赋值
if (props.ref) {
props.ref.current = realDOM;
}
如果ref绑定到类组件元素
// react-dom/src/index.ts mountClassComponent
if (ref) {
ref.current = instance;
}