# react-base
**Repository Path**: pingpingjianvxu/react-base
## Basic Information
- **Project Name**: react-base
- **Description**: 简单实现 React16.8 版本的一些API
- **Primary Language**: JavaScript
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 1
- **Forks**: 0
- **Created**: 2022-03-11
- **Last Updated**: 2022-05-06
## Categories & Tags
**Categories**: Uncategorized
**Tags**: Node, JavaScript, JSX, React
## README
# react-base
# 1. 什么是 React?
- React 是一个用于构建用户界面的 JS 库,核心专注于视图,目的实现组件化开发
# 2. 组件化的概念
- 我们可以很直观的将一个复杂的界面分割成若干个单独组件,每个组件包含自己的逻辑和样式,再将这些独立组件组合完成一个复杂的页面,这样既减少了逻辑复杂度,又实现了代码的重用
1. 可组合:一个组件可以和其他的组件一起使用或者可以直接嵌套再另一个组件内部
2. 可宠用:每个组件都是由独立功能的,可以被使用再多个场景中
3. 可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护
# 3. 搭建 React 开发环境
- npm i create-react-app -d
- create-react-app reactBase
- cd reactBase
- pnpm start
# 4. JSX
## 4.1 什么是 JSX
- 是一种 JS 和 HTML 混合的语法,将组件的结构,数据甚至样式都聚合在一起定义组件
```javascript
React.render(
Hello React
,
document.getElementById('root')
)
```
## 4.2 什么是元素
- JSX 其实只是一种语法糖,最终会通过 babeljs 转译成 createElement 语法
- React 元素是构成 React 应用的最小单位
- React 元素用来描述你再屏幕上看到的内容
- React 元素事实上是普通的 JS 对象,ReactDOM 来确保浏览器中的 DOM 数据和 React 元素保持一致
# 5 组件 & Props
- 可以将 UI 组件切分成独立的,可复用的组件,这样就只需专注于构建每一个独立的部件
- 组件从概念上类似于 JS 函数,它接收任意的入参 props,并返回描述页面展示内容的 React 元素
## 5.1 函数(定义的)组件
- 函数组件接收一个单一的 props 对象并返回一个 React 元素
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
function Welcome(props){
return Hello,{props.name}
}
```
## 5.2 类(定义的)组件
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Welcome extends React.Component {
render(){
return Hello,{this.props.name}
}
}
ReactDOM.render(, root)
```
## 5.3 组件渲染
- React 元素不耽可以是DOM标签,还可以是用户自定义的组件
- 当 React 元素作为用户自定义组件时,它会将 JSX 所接受的属性(attributes)转化为单个对象传递给组件,这个对象被称为 props
- 组件名称必须以大写字母开头
- 组件必须在使用的时候定义或者引用它
- 组件的返回值只能有一个根元素
# 6 状态
- 组件的状态来源于两个地方(分别是属性对象和状态对象)
- 属性是父组件传递过来的(默认属性,属性校验)
- 状态是自己内部的,改变的唯一方式就是 setState
- 属性和状态的变化都会影响视图更新
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
interface Props {
}
interface State {
date: any
}
class Clock extends React.Component {
timerID
constructor(props){
super(props);
this.state = {
date: new Date()
}
}
componentDidMount(){
this.timerId = setInterval(()=>{
this.tick()
}, 1000)
}
componentWillUnmount(){
clearInterval(this.timerID)
}
tick(){
this.setState({
date: new Date()
})
}
render(){
return 当前时间{this.state.date}
}
}
```
## 6.1 不要直接修改 State
- 构造函数是唯一可以给 this.state 赋值的地方
## 6.2 State 的更新可能是异步的
- 处于性能考虑,React 可能会把多个 staState 调用合并成一个调用
- 因为 this.props 和 this.state 可能会异步更新,所以不要依赖他们的值更新下一个状态
- 可以让 setState 接收一个函数而不是一个对象,这个函数上用上一个state作为第一个参数
```javascript
hanlde(){
this.setState((state)=>(
number: state.number + 1
))
}
```
## 6.3 state 的更新可能会被合并
- 当调用 setState 的时候,React 会把提供的对象合并到当前的 state
```javascript
class Counter extends React.Component{
constructor(){
super();
this.state = {
number: 0
}
}
handleClick(){
this.setState({
number: this.state.number+1
})
console.log(this.state.number) // 0
this.setState({
number: this.state.number+1
})
console.log(this.state.number) // 0
this.setState({
number: this.state.number+1
})
console.log(this.state.number) // 0
// 基于上一个state的值更新
this.setState(state=>({
number: state.number + 1
}), () => {
// 更新全部结束后的回调
console.log(this.state.number)
})
}
render(){
return (
{this.state.number}
)
}
}
class Updater{
constructor(){
this.queue = []
this.state = {
number: 0
}
}
steState(newState){
this.queue.push(newState)
}
flush(){
this.queue.forEach(update=>{
if( typeof update === 'function' ){
this.state = {
...this.state,
...update(this.state)
}
}else{
this.state = {
...this.state,
...update
};
}
})
}
}
let state = new Updater();
state.steState({
number: 1
})
state.steState((previouState) => ({
number: previouState.number + 1
}))
state.flush();
```
# 7 事件处理
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写
- 使用 JSX 写法时,需要传入一个函数作为事件处理函数,而不是一个字符串
- 不能通过返回 false 的方式阻止默认行为,必须显式的使用 preventDefault
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Link extends React.Component {
handleClick(e){
e.preventDefault();
alert('the link was clicked')
}
render(){
return (
点击我啦
)
}
}
```
## 7.1 this
- 必须谨慎对待 JSX 回调函数中的 this,可以使用:
- 公共属性(箭头函数)
- 匿名函数
- bind 进行绑定
## 7.2 Event 事件代理
- 原生事件比如说 click change 等会自带一个 event 对象,这个是浏览器给的,但是再 React 中的 event,是经过 React 封装之后重新定义的对象
- 在 React16 中,React 把事件代理到 document 上
- React 17 则是代理到了根节点上
- 事件代理的作用在于,可以让 React 实现批量更新,并且能对事件对象进行回收和缓存
实现原理如下:
```javascript
import { updateQueue } from './Component';
/**
* @param {*} dom 当前绑定的 DOM 元素
* @param {*} eventType 事件类型
* @param {*} listener 事件处理函数
*/
// 为什么需要合成事件 它的作用是什么
/**
* 1. 可以实现批量更新
* 2. 可以实现事件对象的缓存和回收(事件对象用完就销毁)
*/
// 这里是在我们在 updateProps 函数中去对事件赋值的时候调用 addEvent 函数
export function addEvent(dom, eventType, listener){
let store = dom.store || (dom.store = {});
// store.onclick = hanldeClick
store[eventType] = listener;
// eventType.slice(2) onclick => click
// 判断 document 上是否存在同一类型的事件
// document.addEventListener(eventType.slice(2), dispatchEvent, false)
if( !document[eventType] ){
document[eventType] = dispatchEvent
}
}
function dispatchEvent(event){
// event => 原生的 DOM 事件对象
let {target, type} = event;
let eventType = `on${type}`; // click => onclick
// 开启异步更新模式
updateQueue.isBatchUpdate = true;
let { store } = target;
let listener = store && store[eventType];
if(!listener){
return
}
let SyntheicEvent = createSyntheicEvent(event);
listener.call(target, SyntheicEvent)
// 函数执行完 清空
Object.keys(SyntheicEvent).forEach(key=>{
SyntheicEvent[key] = null;
})
// 开启批量更新模式
updateQueue.isBatchUpdate = false;
}
function createSyntheicEvent(nativeEvent){
let SyntheicEvent = {};
Object.keys(nativeEvent).forEach(key=>{
SyntheicEvent[key] = nativeEvent[key]
})
return SyntheicEvent
}
```
## 7.3 Ref
- Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素
- 在 React 渲染生命周期时,在表单元素上的 value 将会覆盖 DOM 节点中的值,在非受控组件中,经常希望 React 能赋予组件一个初始值,但是不去控制后续的更新,这种情况下,我们可以指定一个 defaultValue 属性而不是 value
### 7.3.1 ref 的值是一个字符串
```javascript
import React from 'react';
class Sum extends React.Component{
handleAdd = (event) => {
let a = (this.refs.a).value;
let b = (this.refs.b).value;
(this.rfs.c).value = a + b
}
render(){
return (
<>
>
)
}
}
```
# 8 基本生命周期
- initialization: 初始化 setup props and state 建立属性和状态
- Mounting: componentWillMount(开始挂载) => render(渲染) => componentDidMount(挂载完成)
- Updation: 更新逻辑
props:
componentWillReceiveProps 属性发生改变
shouldComponentUpdate 组件是否更新 默认返回 true
componentWillUpdate 组件即将更新
render 开始重新渲染
componentDidUpdate 组件更新完毕
state:
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
- Unmounting: 组件卸载
componentWillUnmount
## 8.1 父子组件生命周期执行顺序
父组件 setup props => 父组件 componentWillMount => 父组件 render => 子组件 componentWillMount => 子组件 render => 子组件 componentDidMount => 父组件 componentDidMount
## 8.2 生命周期函数副作用
- 由于 react16.8 新增 Fiber 架构,去掉了 will 的所有生命周期函数(除了 componentWillUnmount),那么为什么要去掉这些函数呢,我们在讲解 Fiber 的时候会提到 will 函数的一些副作用
就比如:
1. 我们在 componentWillReceiveProps 中去调用了 setState,那么这个时候由于使用了 setState 函数,导致 componentWillReceiveProps 函数又被触发,就造成了死循环
2. componentWillMount componentWillUpdate 在有了 Fiber 之后,它的挂载跟更新的任务都是可以被打断的,那么如果挂载的任务被打断了,然后 Fiber 将组件重新执行挂载就会又造成 componentWillMount componentWillUpdate 两个函数的重复调用
## 8.3 新版生命周期
- 创建时:constructor - getDerivedStateFromProps - render - componentDidMount
- 更新时: getDerivedStateFromProps - shouldComponentUpdate - getSnapshotBeforeUpdate - componentDidUpdate
- 卸载时:componentWillUnmount
总体分为三个大阶段:
1. render 阶段:纯净且没有副作用,可能会被 React 暂停,中止或者重新启动
2. Pre-commit 阶段:可以读取 DOM
3. Commit 阶段:可以使用DOM,运行副作用,安排更新
### 8.3.1 getDerivedStateFromProps
- static getDerivedStateFromProps(props, state) 这个生命周期的功能实际就是将传入的 props 映射到 state 上
### 8.3.2 getSnapshotBeforeUpdate
- getSnapshotBeforeUpdate() 被调用于 render 之后,可以读取但无法使用 DOM 的时候,它使组件可以在可能更改之前从DOM捕获一些信息(例如滚动位置),此生命周期返回的任何值都将作为参数传递给 componentDidUpdate
# 9 Context 上下文
- 在某些场景下,想在整个组件树中传递数据,但却不想手动的在每一次传递属性,可以直接在 React 中使用强大的 context API 解决上述问题,类似于 vue2 的 provider inject
- 在一个典型的 React 应用中,数据是通过 props 属性自上而下进行传递,但是这种做法对于某些类型的属性而言是极其繁琐的,这些属性是应用程序中许多组件都需要使用,Context 提供了一种在组件之间共享此类值的方法,而不必显示的通过组件树的逐层传递 props
# 10 高阶组件
- 高阶组件就是一个函数,传给它一个组件,它返回一个新的组件
- 高阶组件的作用其实就是为了组件之间的代码复用
```javascript
const NewComponent = higerOrderComponent(oldComponent)
```
## 10.1 属性代理
- 基于属性代理:操作组件的 props
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
const loading = message => oldComponent => {
return class extends React.Component {
render(){
const state = {
show: ()=> {
let div = document.createElement('div');
div.innerHTTML = `
${message}
`
document.body.appendChild(div)
},
hide: ()=> {
document.getElementById('loading').remove()
}
}
return (
)
}
}
}
class Hello extends React.Component {
render(){
return (
hello
)
}
}
let loadingHello = loading('正在加载')(Hello);
ReactDOM.render(, root)
```
## 10.2 反向继承
- 基于反向继承,我们可以拦截生命周期和 state 还有渲染过程
- 在以下场景可以使用,当我们用了第三方UI框架比如,antd 组件库,使用它的某一个标准组件,比如 button 组件,我们可能更改不了它的子内容,那么这个时候,我们就可以用到反向继承,对子内容进行修改
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class MyButton extends React.Component{
state = {
name: '按钮'
}
componentDidMount(){
console.log('Button componentDidMount')
}
render(){
console.log('Button render')
return (
)
}
}
const wrapper = OldComponent => {
// 让新组件继承老组件
return class NewComponent extends OldComponent{
componentDidMount(){
console.log('NewComponent componentDidMount')
// 调用父类的生命周期
super.componentDidMount()
}
render(){
console.log('NewComponent render')
}
}
}
// 这里也可以用装饰器写法
// @wrapper()
// npm i react-app-rewired customize-cra @babel/plugin-propoal-decoratora -s
let NewButton = wrapper(MyButton);
ReactDOM.render(, document.getElementById('root'))
```
### 10.2.1 装饰器支持
- npm i react-app-rewired customize-cra @babel/plugin-proposal-decorators -s
- 修改 package.json
```javascript
"script": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
}
```
- 添加 config-overrides.js
```javascript
const {
override,
addDecoratorsLegacy
} = require('customize-cra');
module.exports = override(
addDecoratorsLegacy()
)
```
- 添加 jsconfig.json
```javascript
{
"compilerOptions": {
"experimentalDecorators": true
}
}
```
- 添加 .babelrc
```javascript
{
"presets": [
"@babel/preset-env"
],
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
]
]
}
```
# 11. render props
- 是指一种在 React 组件之间使用一个值为函数的 props 共享代码的简单技术
- 具有 render props 的组件接收一个函数,该函数返回一个 React 元素并调用它而不是实现自己的渲染逻辑
- render props 是一个用于告知组件需要渲染什么内容的函数 props
- 这也是逻辑复用的一种方式
- 类似于 vue 的插槽效果
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
function withTracker(oldComponent){
return class MounseTracker extends React.Component {
constructor(props){
super(props);
this.state = {
x: 0,
y: 0
}
}
handleMouseMove = (e) => {
this.setState({
x: e.clientX,
y: e.clientY
})
}
render(){
return (
)
}
}
}
// 要求类组件的 render 或者是函数组件 它的返回值必须是一个 React 元素
let HighOrder = withTracker((props)=>(
移动鼠标
当前位置 x = {props.x} y = {props.y}
))
ReactDOM.render(
,
document.getElementById('root')
);
```
## 11.1 HOC React.Fragment
- 我们都知道,在 React 中类的 render 方法或者函数只能返回体格 React 元素,并且只有一个根节点,那么这样就会有个问题,比如说我们在多个组件嵌套之后,发现多出了好多个子组件的根节点,并且可能这些定义的 div 节点是无意义的,更多的标签会增加 DOM 树的负担,那么这个时候我们就可以用到 Fragement 空节点,无需向 DOM 添加额外的节点
```javascript
function Child(props){
return (
{props.number}
)
}
class App extends React.Component {
constructor(props){
super(props);
this.state = {
number: 0
}
}
render(){
return (
)
}
}
// 审查元素时,你就会发现节点 parent 下只有一个 number 内容并无其他节点插入
```
# 13 pureComponent 纯组件
- 当我们对状态或者 props 进行修改,但是并没有对破坏数据的状态,就比如说我们对 state.number + 0 进行修改,当我们继承React.Component 的时候会发现,即使 number 未发生改变,还是会重新进行渲染,这就造成了不必要的消耗,当我们继承 pureComponent 纯函数的时候,你就会发现,number 未发生改变,并不会重新 render
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.PureComponent {
state = {
name: 'zsj',
number: 23
}
hanldeFn = (e, amount) => {
this.setState({
number: this.state.number + amount
})
}
render(){
console.log('render')
return (
名字:{this.state.name}
年龄:{this.state.number}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
- pureComponent 纯函数默认在 shouldComponentUpdate 中进行了新老数据的判断,判断数据是否一致如果不一致则返回 true,一致则返回 false
- 那么 如果我们想在Component 中也实现这样的效果呢
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
state = {
name: 'zsj',
number: 23
}
hanldeFn = (e, amount) => {
this.setState({
number: this.state.number + amount
})
}
// 在这里进行拦截处理 对数据的新老状态进行对比
// 就是对重新渲染进行了优化
// 在 PureComponent 中已经默认帮你实现了这个逻辑
shouldComponentUpdate(nextProps, nextState){
// 对 props 的处理也是如此
let oldKeyLength = Object.keys(this.state).length;
let newKeyLength = Object.keys(nextState).length;
if( oldKeyLength !== newKeyLength ){
return true
}
for( let key in this.state ){
if( this.state[key] !== nextState[key] ){
return true
}
}
return false
}
render(){
console.log('render')
return (
名字:{this.state.name}
年龄:{this.state.number}
)
}
}
ReactDOM.render(
,
document.getElementById('root')
);
```
## 14 hotFix 当处理多个文本子节点
- React babel 编译对静态文本以及动态文本进行分别处理
```javascript
class App extends React.Component {
state = {
number: 1
}
render(){
let element = (
number: 1
)
// 这个时候我们打印出来的 props.children 是一个字符串 number: 1
console.log(element)
return (
element
)
}
}
class App extends React.Component {
state = {
number: 1
}
render(){
let element = (
number: {this.state.number}
)
// 这个时候我们打印出来的 props.children 是一个数组 ['number', '1']
console.log(element)
return (
element
)
}
}
```
# 1. React Hooks
- Hook 是 React16.8 的新增特性,可以让你在不编写 class 类的情况下使用 state 以及其他的 React 特性
- 也就是说我们可以直接在函数组件中去使用 Hook,达到与 class 组件一样的修改 state 状态的效果已经更新效果等
# 2. 解决的问题
- 在组件之间复用状态逻辑很难,可能要用到 render props 和高阶组件,React 需要为共享状态逻辑提供更好的原生途径,Hook 使你在无需修改组件结构的情况下复用状态逻辑
- 复杂组件变得难以理解,Hook 将组件中相关联的部分拆分程更小的组件
- 难以理解的 class 包括难以捉摸的 this 指向问题
# 3. 注意事项
- 只能在函数最外层调用 hook,不要再循环,条件判断或者子函数中使用
- 只能再 React 的函数组件中调用 Hook
# 4. useState
- useState 通过再函数组件里调用它给添加一些内部的 state,React 会再重复渲染时保留这个 state
- useState 会返回一对值,当前状态和一个更新它状态的函数方法,可以再事件处理函数中或者其他一些地方调用这个函数,类似 class 组件的 setState,但是它不会合并新老的状态 state
- useState 唯一的参数就是初始 state
- 返回一个 state,以及更新 state 的函数
* 再初始渲染期间,返回的状态与传入的第一个参数值相同
* setState 函数用于更新 state,它接收一个新的 state 并将组件的一次重新渲染加入队列
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
// 简单实现 useState
let lastState;
function useState(initialState){
lastState = lastState || initialState;
function setState(newState){
lastState = newState;
render()
}
return [
lastState,
setState
]
}
let lastRef;
function useRef(){
lastRef = lastRef || {current: null};
return lastRef
}
function render(){
ReactDOM.render(
,
document.getElementById('root')
);
}
// 类组件需要创建实例 性能较差
// 每次组件渲染都是一个独立的闭包
function App(){
let numberRef = useRef();
function alertNumber(){
setTimeout(()=>{
alert(numberRef.current); // 这里的 number 永远指向当前渲染的number,而不会指定最新的 number
}, 100)
}
let [number, setNumber] = useState(0);
return (
{number}
)
}
render()
```
## 4.1 函数式更新
```javascript
import { useState } from 'react';
function App(){
let [number, setNumber] = useState(0);
return (
{number}
)
}
```
## 4.2 惰性初始 state
```javascript
import { useState } from 'react';
function App(){
let [number, setNumber] = useState(()=>{
return {
count: 0
}
});
return (
{number.count}
)
}
```
## 4.3 性能优化
### 4.3.1 Object.is
- 调用 state hook 的更新函数并传入当前的 state 时,React 将跳过子组件的渲染以及 effect 的执行(React 使用 Object.is 比较算法来比较 state)
```javascript
import React, {
useState, memo
} from 'react';
import ReactDOM from 'react-dom';
let Child = ({data, handleClick}) => {
console.log('child render')
return (
)
}
Child = memo(Child);
// 子组件用到的参数需要放在父函数外面,因为如果放在父函数里面的话
// 父组件就会重新设置参数 并且引入到子组件中,造成了子组件重新渲染
let handleClickFn = () => {}
let testData = {
number: 1
}
function App(){
const [number, setNumber] = useState(0);
const [name, setName] = useState('zsj');
const handleClick = (event) => {
setNumber(x=>x+1)
}
return (
setName(event.target.value)} />
)
}
ReactDOM.render(
,
document.getElementById('root')
);
```
### 4.3.2 减少渲染次数
- 把内联回调函数以及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
- 把创建函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时蔡虎i重新计算 memoized 值,这种优化有助于避免在每次渲染时都进行高开销的计算
```javascript
import React, {
useState, memo, useMemo, useCallback
} from 'react';
import ReactDOM from 'react-dom';
let hookStates = [];
let hookIndex = 0;
function useState(initialState){
hookStates[hookIndex] = hookStates[hookIndex] || initialState;
let currentIndex = hookIndex;
function setState(newState){
hookStates[currentIndex] = newState;
render();
}
return [
hookStates[hookIndex++],
setState
]
}
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
}
}
let newMemo = factory();
hookStates[hookIndex++] = [newMemo, deps]
return newMemo
}
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
}
}
hookStates[hookIndex++] = [callback, deps]
return callback
}
let Child = ({data, handleClick}) => {
console.log('child render')
return (
)
}
Child = memo(Child)
function App(){
console.log('App render')
const [number, setNumber] = useState(0);
const [name, setName] = useState('zsj');
let handleClick = useCallback((event) => {
setNumber(number+1)
}, [number])
let data = useMemo(()=>({
number
}), [number])
return (
setName(event.target.value)} />
)
}
function render(){
hookIndex = 0;
ReactDOM.render(
,
document.getElementById('root')
);
}
render();
```
# 4.4 useReducer
- useState 的替代方案,它接收一个形如 (state, action) => newState 的 reduer,并返回当前的 state 以及与其配置的 dispatch 方法
- 在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较为复杂并且包含多个子值,或者下一个 state 依赖于之前的 state 等
## 4.4.1 基本用法
```javascript
const [state, dispatch] = useReducer(reducer, initialArg, init)
```
- 实例应用以及实现
```javascript
import ReactDOM from 'react-dom';
let hookStates = [];
let hookIndex = 0;
function useReducer(reducer, state, initialState){
hookStates[hookIndex] = hookStates[hookIndex] || (initialState ? initialState(state) : state)
let currentIndex = hookIndex;
function dispatch(action){
hookStates[currentIndex] = reducer ? reducer(hookStates[currentIndex], action) : action
render()
}
return [hookStates[currentIndex], dispatch]
}
// useState 只是一个简化版的 useReduer 是一个语法糖
function useState(initialState){
return useReducer(null, initialState)
}
// 处理函数 状态变更函数
/**
* 接收一个老状态和一个 action 动作,返回一个新状态
*/
function reducer(state, action){
switch(action.type){
case 'ADD':
return {
number: state.number+1
}
case 'MINUS':
return {
number: state.number-1
}
default:
return state
}
}
let initialState = 0;
function init(){
return {
number: initialState
}
}
function App(){
let [ state, dispatch ] = useReducer(reducer, initialState, init);
return (
{state.number}
)
}
function render(){
ReactDOM.render(
,
document.getElementById('root')
);
}
render();
```
# 4.5 useContext 接收上下文
- 接收一个 context 对象(React createContext 的返回值),并返回该 context 的当前值
- 当前的 context 值由上层组件中距离当前组件最近的 MyComponent.Provider 的 value prop 决定
- 当组件上层最近的 MyComponent.Provider 更新时,该 hook 会触发重渲染,并使用最新传递给 MyComponent.Provider 的 context value 值
- useContext(context) 相当于组件的 static contextType = myContext 或者 MyComponent.Consumer
- useContext(context) 只是让你能够读取 context 的值以及订阅 context 的变化,仍然需要在上层组件树使用 MyComponent.Provider 来为下层组件提供 context
## 4.5.1 useContext 基础应用
```javascript
import React, {
createContext,
useReducer,
useContext
} from 'react';
import ReactDOM from 'react-dom';
let ContentText = createContext();
function reducer(state, action){
switch(action.type){
case 'ADD':
return {
number: state.number+1
}
case 'MINUS':
return {
number: state.number-1
}
default:
return state
}
}
let initialState = 0
function init(){
return {
number: initialState
}
}
function Counter(){
let { state, dispatch } = usecContext(ContentText);
return (
{state.number}
)
}
function App(){
let [state, dispatch] = useReducer(reducer, initialState, init);
return (
)
}
```
# 4.6 useEffect
- 在函数组件主题内(这里指在 React 渲染阶段)改变 DOM,添加订阅,设置定时器,记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 而破坏 UI 的唯一性
- 使用 useEffect 完成副作用操作,赋值给 useEffect 的函数会在组件渲染到屏幕之后执行,可以把 effect 看作从 React 的函数式世界通往命令式世界的逃生通道
- useEffect 就是一个 effect hook,给函数组件增加了操作副作用的能力,它跟 class 组件中的 componentDidMount componentDidUpdate componentWillUnmount 具有相同的用途,只不过合并成了一个 API
- 该 hook 接收一个包含命令式,且可能由副作用代码的函数
## 4.6.1 基础应用
```javascript
useEffect(didUpdate)
```
## 4.6.2 用 class 组件和函数组件实现修改标题
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props);
this.state = {
number: 0
}
}
componentDidUpdate(){
document.title = `你点击了${this.state.number}`
}
componentDidMount(){
document.title = `你点击了${this.state.number}`
}
render(){
return (
{this.state.number}
)
}
}
// 用函数组件改造
function FunctionCounter(){
let [number, setNumber] = React.useState(0)
// useEffect 里的函数会在第一个挂载之后和每次更新之后都会执行
React.useEffect(()=>{
document.title = `你点击了${number}次`
})
return (
{number}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
## 4.6.3 跳过 effect 进行性能优化
- 如果某些特定值在两次重新渲染之前都没发生变化,可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可
- 如果想执行只运行一次的 effect(仅在组件挂载和卸载时运行),可以传递一个空数组作为第二个参数,这就告诉了 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
class Counter extends React.Component {
constructor(props){
super(props);
this.state = {
number: 0
}
}
componentDidUpdate(){
document.title = `你点击了${this.state.number}`
}
componentDidMount(){
document.title = `你点击了${this.state.number}`
}
render(){
return (
{this.state.number}
)
}
}
// 用函数组件改造
function FunctionCounter(){
let [number, setNumber] = React.useState(0)
// useEffect 里的函数会在第一个挂载之后和每次更新之后都会执行
// 什么是副作用?当我们更新了状态,会执行 Effect 中的函数
// 那么当我们重复修改状态,就造成了这个函数重复执行,就创建了多个定时器
// 造成渲染出问题 这就是所谓的副作用
// 那么 如何解决这个问题,就是说我只要求执行一次,状态更新的时候不再执行副作用
// 我们可以在 useEffect 的第二个参数中,传入空数组,代表当前副作用的依赖项永远不变
React.useEffect(()=>{
console.log(`开启新得定时器`)
let timer = setInterval(()=>{
setNumber(number=>number+1)
}, 1000)
return () => {
// 这里是执行挂载之后执行下一次的副作用函数之前 执行的内容
// 清除副作用
console.log('销毁定时器')
clearInterval(timer)
}
}, [number]); // 第二个参数代表依赖项 就是说只针对某个属性状态的修改 再次触发
return (
{number}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 4.7 使用 useRef 获取最新的值
```javascript
import React from 'react';
import ReactDOM from 'react-dom';
function Counter(){
let [number, setNumber] = React.useState(0);
// useRef 会返回一个可变的 ref 对象 { current: null }
// ref 对象在组件的整个生命周期内保持不变
let lastNumber = React.useRef();
let alertNumber = ()=>{
setTimeout(()=>{
alert(lastNumber.current)
}, 3000)
}
// 在每次渲染之后 保证 number 的值是最新的
React.useEffect(()=>{
lastNumber.current = number;
})
return (
{number}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
## 4.7.1 forwardRef
- 将 ref 从父组件中转发到子组件中的 dom 元素上
- 子组件接收 props 和 ref 作为参数
```javascript
import React, {
useState,
useRef,
} from 'react';
import ReactDOM from 'react-dom';
// forwardRef 转发 Ref
// 因为函数组件没有实例 如果是类组件可以在来组件的属性上赋值 ref
// 这个 ref 指向的式类组件的实例
function Child(props, ref){
return (
)
}
const ForwardFunctionChild = React.forwardRef(Child)
function Parent(){
let [number, setNumber] = useState(0);
const functionChildRef = useRef();
return (
{number}
)
}
function forwardRef(FunctionChild){
return class extends React.Component {
render(){
return
}
}
}
ReactDOM.render(
,
document.getElementById('root')
)
```
## 4.7.2 useImperativeHandle
- 可以让你在使用 useRef 时自定义暴露给父组件的实例
- 在大多数情况下,应当避免使用 ref 这样的命令式代码,useImperativeHandle 应该与 forwardRef 一起使用
```javascript
import React, {
useRef,
forwardRef,
useImperativeHandle
} from 'react';
import ReactDOM from 'react-dom';
function ChildComponent(props, ref){
let childRef = useRef();
useImperativeHandle(ref, ()=>(
// functionChildRef =>
{
// 拦截父组件的操作,提供给父组件能用的方法
focus(){
console.log(1111)
}
}
))
return (
)
}
const ForwardFunctionChild = forwardRef(ChildComponent)
function Parent(){
let functionChildRef = useRef();
const focusHandle = () => {
// 我们在这里可以拿到子组件的 input DOM 元素
// 那么我们拿到 DOM 元素之后,就可以随意操作 DOM 元素,比如说直接删除它
// 这个时候,我们想让父组件对 DOM 的操作仅限于某些方法
functionChildRef.current.focus()
}
return (
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 4.8 useLayoutEffect
- 其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
- useEffect 不会阻塞浏览器渲染,而 useLayoutEffect 会阻止浏览器渲染
- useEffect 会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行
在讲解之前,我们先来了解一下事件循环机制
## 4.8.1 事件循环
1. 从宏任务队列中取出一个宏任务执行
2. 检查微任务队列,执行并清空微任务队列,如果在微任务的执行中又加入了新的微任务,则会继续执行新的微任务
3. 进入更新渲染阶段,判断是否需要渲染,要根据屏幕刷新率,页面性能,页面是否在后台运行来共同决定,通常来说这个渲染间隔是固定的,一般为 60 帧/秒
4. 如果确定要更新会进入下面的步骤,否则本循环取消
- 如果窗口大小发生了变化,执行监听的 resize 事件
- 如果页面发生了滚动,执行 scroll 方法
- 执行动画帧回调,也就是 requestAnimationFrame 回调
- 重新渲染用户界面
5. 判断是否宏任务和微任务队列为空,则判断是否执行 requestdleCallback 的回调函数
## 4.8.2 基本使用
```javascript
import React, {
useRef,
useEffect,
useLayoutEffect
} from 'react';
import ReactDOM from 'react-dom';
// useLayoutEffect 与 useEffect 最大的区别就是,
// useLayoutEffect 是微任务,它会在浏览器渲染完毕之前执行,但是这个时候 DOM 状态已更新,那么就是说
// 如果我们在这个 useLayoutEffect 函数中有阻断内容,那么浏览器就会阻断渲染
const Animation = () => {
let divRef = useRef();
let style = {
width: '100px',
height: '100px',
backgroundColor: 'red'
}
// useEffect 可以理解为一个宏任务,在微任务执行完之后再执行,也就是说,它会在渲染完毕之后执行
useEffect(()=>{
divRef.current.style.transform = 'translate(500px)';
divRef.current.style.transition = 'all 500ms';
}, [])
// useLayoutEffect 可以理解为一个微任务 按照事件循环机制 会先执行微任务,也就是说,这个useLayoutEffect 函数中的内容会先执行
// 也就是说,它会在渲染完成之前执行,但是这个时候我们是可以拿到 DOM 更新之后的状态的
useLayoutEffect(()=>{
divRef.current.style.transform = 'translate(500px)';
divRef.current.style.transition = 'all 500ms';
})
const getNode = () => {
console.log(divRef.current)
}
return (
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 5. 自定义 Hook
- 有时候我们会想要在组件之间重用一些状态逻辑
- 自定义 Hook 可以在不怎会更加组件的情况下达到同样的目的
- Hook 是一种复用状态逻辑的方式,它不复用 state 本身
- 事实上 Hook 的每次调用都有一个完全独立的 state
- 自定义 Hook 更像是一种约定,而不是一种功能,如果函数的名字以 use 开头,并且调用了其他的 Hook,则就称其为自定义 Hook
# 5.1 实现一个自定义计数器
```javascript
import React, {
useState,
useEffect
} from 'react';
import ReactDOM from 'react-dom';
// 这里要注意,当你在函数中使用 Hook 的时候,如果是自定义的 Hook,必须以 use 开头
// 每个 Hook 包含一个独立的 state 状态
// 自定义 hook 可以理解为是对重复代码的复用封装
function useNumber(initialState){
let [number, setNumber] = useState(initialState);
useEffect(()=>{
setTimeout(() => {
setNumber(number+1)
}, 1000);
}, [])
return [number, ()=>setNumber(x=>x+1)]
}
function Timer1(){
let [number, add] = useNumber(0);
return (
{number}
)
}
function Timer2(){
let [number, add] = useNumber(10);
return (
{number}
)
}
const App = () => {
return (
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 1. ReactRouter 路由原理
- 不同的路径渲染不同的组件
- 有两种实现方式
1. HashRouter 利用 hash 实现路由切换
2. BrowserRouter 实现 h5 API 实现路由的切换
## 1.1 HashRouter
- 利用 hash 实现路由切换
```javascript
// 当浏览器的 hash 只发生变化 会执行回调函数
window.addEventListener('hash', function(){
console.log(window.location.hash);
let pathName = window.location.hash.slice(1);
root.innerHTML = pathName;
})
```
## 1.2 BrowserRouter
- 利用 h5 API 实现路由的切换
### 1.2.1 history#
- history 对象提供了操作浏览器会话历史的窗口
- historylength 属性声明了浏览器历史列表中的元素数量
- pushState HTML5 引入了 history.pushState 和 history.replaceState 方法,分别可以添加和修改历史记录条目,这些方法通过与 window.onpopstate 配合使用
- onpopstate window.onpopstate 是 popstate 事件在 window 对象上的事件处理程序
### 1.2.2 pushState
- pushState 会往 history 中写入一个对象,造成的结果便是 hoistroy.length + 1,url改变。该索引 history 对应有一个 state 对象,这个时候若是点击浏览器的后退,便会触发 popstate 事件,将刚刚的存入数据对象清除
- pushState 会改变 history
- 每次使用的时候会为该索引的 state 引入我们自定义数据
- 每次我们会根据 state 的信息还原当前的 view,于是用户点击后退便有了与浏览器后退前进一致的感受
- pushState 需要三个参数:一个状态对象,一个标题(目前被忽略),和一个URL
- 调用 history.pushState() 或者 history.replaceState 不会触发 popState 事件,popState 事件只会在浏览器某些行为下触发,比如点击后退,前进按钮或者在 JS 中调用 histroy.back() history.forward() history.go() 方法
```javascript
window.onpopstate = (e) => {
console.log(window.location.hash);
let pathName = window.location.hash.slice(1);
root.innerHTML = pathName;
}
// 自定义重写 pushState
let historyObj = window.history;
(function(historyObj){
let oldPushState = history.pushState;
history.pushState = function(state, title, pathname){
let result = oldPushState.apply(history, arguments);
if( typeof window.onpopstate == 'function' ){
window.onpopstate({
state, pathname, type: 'pushstate'
})
}
return result
}
})(historyObj)
```
# 2. 跑通路由
- createBrowserHistory
- createHashHistory
```javascript
pnpm i react-ruter-dom -s
```
# 1. redux 应用场景
- 随着 JS 单页面应用开发日趋复杂,管理不断变化的 state 非常困难
- Redux 的出现就是为了解决 state 里的数据问题
- 在 React 中,数据在组件中是单向流动
- 数据从一个方向父组件流向子组件,由于这个特性,两个非父子关系的组件之间的通讯就比较麻烦
类似于我们在 Vue 中使用的 vuex,数据共享
在非 Redux 方式中:
父组件通过 props 传递给它的两个子组件,也就是说,我们想要在这两个子组件之间通信就很麻烦,比如我想要调用兄弟组件的方法或者数据等
在 Redux 方法中:
通过 redux 的 store 传递给两个子组件,把两个子组件相互通信的一些方法或者数据通过 store 连接
# 2. Redux 设计思想
- Redux 是将整个应用状态存储到一个地方,称为 store
- 里面保存一颗状态树 state tree
- 组件可以派发 dispatch 行为 action 给 store,而不是直接修改 store 中的状态
- 其他组件可以通过订阅 store 中的状态,来刷新自己的视图
# 3. redux 应用(这里的版本是 4.2.0)
```javascript
// 老版的可以直接使用 createStore
import {
legacy_createStore as createStore
} from 'redux';
/**
* store
* 1. 获取仓库中的状态 store.getState()
* 2. 向仓库派发动作 store.dispatch({type: 'ADD'})
* 3. 仓库收到动作 会把动作和佬状态传给 reducer(处理器或者说是过滤函数)来计算新的状态
*/
/**
* @params {*} state 佬状态
* @params {*} action 动作
*/
const ADD = 'ADD';
const MINUS = 'MINUS';
function reducer(state={number: 0}, action){
switch(action.type){
case ADD:
return {
number: state.number+1
}
case MINUS:
return {
number: state.number-1
}
default:
return {
...state
}
}
}
let store = createStore(reuder)
// 派发
function Add(){
store.dispatch({
type: 'ADD'
})
}
setTimeout(() => {
Add()
console.log(store.getState()); // {number: 1}
}, 1000);
// 获取到初始值
console.log(store.getState()); /// {number: 0}
```
# 4. redux 三大原则
- 整个应用的 state 被存储在一颗 object tree 中,并且整个 object tree 只存在于唯一一个 store 中
- state 是只读的,唯一能改变 state 的方法就是触发 action,action 是一个用于描述易发生事件的普通对象,使用纯函数来执行修改,为了描述 action 如何改变 state tree,需要编写 reducers
- 单一数据源的设计让 React 的组件之间的通信更加方便,同时也便于状态的统一管理
- reducer 必须是一个纯函数
- redux 的简单实现
```javascript
export function createStore(reducer){
// state 初始状态
// listeners 用于存放订阅函数
let state, listeners = [];
const getState = () => {
return state
}
const dispatch = (action) => {
state = reducer(action);
listeners.forEach(listener => listener())
}
const subscribe = (listener) => {
listeners.push(listener)
// 当订阅函数存放过多 我们会调用这个返回的函数 释放
// 也就是取消订阅的方法
// 一般来说一个组件挂载成功后,会添加订阅
// 当一个组件卸载后,会取消订阅
return function(){
listeners.filter(l=>l != listener)
}
}
// 在源码中 Redux 会自动调用一次 dispatch 用于拿初始状态
dispatch({type: '@REDUX/INIT'})
return {
getState,
dispatch,
subscribe
}
}
```
# 5. 在 react 中使用 redux
```javascript
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { createStore } from './studyRedux/redux';
function reducers(state={number: 0}, action){
switch(action.type){
case 'ADD':
state.number = state.number + 1;
return state
default:
return state
}
}
let store = createStore(reducers);
function Counter(){
let [number, setNumber] = useState(0);
const handle = () => {
store.dispatch({
type: 'ADD',
state: number+1
})
setNumber(store.getState().number)
}
useEffect(()=>{
console.log(store.getState().number)
}, [number])
return (
当前节点:{number}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 6. bindActionCreators
- bindActionCreators 就是将单个或者多个 actionCreators 转化为 dispatch(action) 的函数集合形式
- 可以不在手动写 dispatch 方法,而是直接调用即可,就相当于做了一层包装
```javascript
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
import { legacy_createStore as createStore, bindActionCreators } from 'redux';
function reducers(state={number: 0}, action){
switch(action.type){
case 'ADD':
state.number = state.number + 1;
return state
default:
return state
}
}
const add = () => {
return {
type: 'ADD'
}
}
let store = createStore(reducers);
let bounAadd = bindActionCreators(add, store.dispatch);
function Counter(){
let [number, setNumber] = useState(0);
const handle = () => {
bounAadd()
setNumber(store.getState().number)
}
useEffect(()=>{
console.log(store.getState().number)
}, [number])
return (
当前节点:{number}
)
}
ReactDOM.render(
,
document.getElementById('root')
)
```
# 7. combinReducers
- redux 规定,只能有一个仓库,仓库里职能由一个状态,也只能有一个 reducer,那么当我们攥写大型项目的时候,这样的设计就很难维护
- combinReducers 则帮我们解决了这样的问题,我们可以合并多个 reducer 方法,然后它会返回合并后的 reducers 集合注入到 createStore 中,那么我们就可以在 store 里调用我们分别定义的 reducer 方法,并于后续的状态变更维护
```javascript
import {
legacy_createStore as createStore,
combineReducers
} from 'redux'
// 当我们存在多个 state 多个 reducer 需要去维护
function reducerForOne(state={number: 0}, action){
switch(action.type){
case 'ADD':
return {
number: state.number + action.payload
}
default:
return state
}
}
function reducerForTwo(state={age: 0}, action){
switch(action.type){
case 'MINUT':
return {
age: action.payload
}
default:
return state
}
}
let reducers = {
// 指定合并后的 state 的 key 值
reducerForOne: reducerForOne,
reducerForTwo: reducerForTwo
};
// combineReducers 不只是对 reducer 进行合并 对 state 状态也进行了合并
let combinedReducers = combineReducers(reducers)
const store = createStore(combinedReducers)
store.dispatch({
type: 'ADD',
payload: 1
})
store.dispatch({
type: 'MINUT',
payload: 28
})
console.log(store, store.getState())
// 原理实现
function combinReducers(reducers){
return function(state, action){
let nextState = {};
Object.keys(reducers).forEach(key=>{
nextState[key] = reducers[key](state[key], action)
})
return nextState
}
}
```
# 1. react-redux
- 用于嵌入 react 中的 redux 插件,这个库有以下两个组件
- Provider.js
- connect.js
# react-fiber
## 1. 屏幕刷新
- 目前大多数设备的屏幕刷新率为 60次/秒
- 浏览器渲染动画或页面的每一帧的速率也需要跟设备屏幕的刷新率保持一致
- 页面是一帧一帧绘制出来得,当每秒绘制的帧数达到 60,页面是流畅的,小于这个值,则会卡顿
- 每个帧的预算时间是 16.66 毫秒 1秒/60
- 1s 60帧 所以每一帧分到的时间是 1000/60 16ms,所以我们书写代码时力求不让一帧的工作量超过 16 ms
## 2. 帧
- 每个帧的开头包括样式计算,布局和绘制
- JS 执行 JS 引擎和页面渲染引擎在同一个渲染线程,GUI渲染和 JS 执行两者是互斥的
- 如果某个任务执行时间过长,浏览器会推迟渲染
### 2.1 一个帧具体执行了哪些
1. 阻塞输入事件 touch wheel 非阻塞输入事件 click keypress (input events 输入事件)
2. timers 定时器(JS)
3. window resize scroll media query change (begin Frame 开始帧)
4. requestAnimationFrame 请求动画帧
5. 计算样式 更新布局
6. compositing Update Paint invalidation Record (绘制)
7. idleCallback1 idleCallback1 (空闲时间执行)
## 3.rAF
- requestAnimationFrame 回调函数会在绘制之前执行
## 4.requestIdleCallback
- 我们希望快速响应用户,让用户觉得够快,不能阻塞用户的交互
- requestIdleCallback 使开发者能够在主时间循环上执行后台和低优先级的工作,而不会影响延迟关键事件,如动画和输入响应
- 正常帧任务完成后没超过 16 ms,说明时间有富余,此时就会执行 requestIdleCallback 里注册的任务
```javascript
// 这是一个全局属性,作为用户告诉浏览器,现在执行 callback 函数,但是它的优先级较低,告诉浏览器可以在空闲的时候执行 callback
// 但是如果到了超时时间了,就必须马上立刻执行
window.requestIdleCallback(callback, {
timeout: 1000
})
// callback(deadline) deadline 是一个对象有两个属性
// timeRemaining() 可以返回此帧还剩下多少时间可以供用户使用
// didTimeout 此callback任务是否超时
```
## 5.单链表
- 单链表是一种链式存取的数据结构
- 链表中的数据是以节点来标识,每个节点的构成:元素+指针(指示后继元素存储位置),元素是存储数据的存储单元,指针是连接每个节点的地址
```javascript
// 实现一个单链表数据更新
// 实现更新队列单链表
class Update {
constructor(payload, nextUpdate){
this.payload = payload;
this.nextUpdate = nextUpdate;
}
}
class UpdateQueue {
constructor(){
this.lastUpdate = null;
this.firstUpdate = null;
this.baseState = null;
}
enqueueUpdate(update){
if( this.firstUpdate == null ){
this.firstUpdate = this.lastUpdate = update;
}else{
this.lastUpdate.nextUpdate = update;
this.lastUpdate = update;
}
}
// 获取佬状态 遍历链表进行更新
forceUpdate(){
let currentState = this.baseState || {};
let currentUpdate = this.firstUpdate;
while(currentUpdate){
// 获取最新的状态
let payloadState = typeof currentUpdate.payload === 'function' ?
currentUpdate.payload(currentState) : currentUpdate.payload;
currentState = {
...currentState,
...payloadState
}
currentUpdate = currentUpdate.nextUpdate;
}
this.baseState = currentState;
this.firstUpdate = this.lastUpdate = null;
return currentState
}
getState(){
return this.baseState
}
}
let queue = new UpdateQueue();
queue.enqueueUpdate(new Update({name: 'zsj'}));
queue.enqueueUpdate(new Update({number: 1}));
queue.enqueueUpdate(new Update((state)=>{
state.number = state.number + 1
return state
}));
queue.forceUpdate();
console.log(queue.getState().number)
```
## 6.Fiber
- 在 React16.8 之前,React会递归对比 VDOM 树,找到需要变动的节点,然后同步更新它们,这个过程 React 成为协调
- 在协调期间,React 会一直占用着浏览器资源,一则会导致用户触发的事件得不到响应,二则会导致掉帧,用户可能会感觉到卡顿
- 这种遍历是递归调用,执行栈会越来越深,导致无法中断,性能很差
```javascript
// 我们模拟一下VDOM递归
let fiber = {
key: 'A1',
children: [
{
key: 'B1',
children: [
{
key: 'C1',
children: []
},{
key: 'C2',
children: []
},
]
},
{
key: 'B2',
children: []
}
]
}
function Work(vdom){
console.log(vdom.key)
}
function walk(vdom){
Work(vdom)
vdom.children.forEach(child=>{
walk(child)
})
}
walk(fiber)
```