likes
comments
collection
share

React-基础总结

作者站长头像
站长
· 阅读数 20

react 是什么?

React是一个声明式,高效且灵活的用于构建用户界面的JavaScript库;

使用React可以将一些简短,独立的代码片组合成复杂的UI界面,这些代码片段就是组件

  • 一个用于构建用户界面的 js 库
  • 一个将数据渲染为 HTML 视图的开源 js 库

react 由 Facebook 开发且开源

为什么要学?

  1. 原生 js 操作 DOM 繁琐,效率低(DOM API 操作 UI)
  2. 使用 js 直接操作 DOM,浏览器会进行大量的重绘重排
  3. 原生 js 没有组件化编码方案,代码重用率低

react 特点

  1. 采用 组件化 模式,声明式编码,提高开发效率和组件复用率
  2. 在 ReactNative 中可以使用 React 语法进行移动端开发
  3. 使用 虚拟DOM + 优秀的 Diffing 算法,尽量减少与真实 DOM 的交互

环境搭建

  1. 全局安装 create-react-app: npm install -g create-react-app
  2. 使用脚手架创建 react 项目: create-react-app react-demo

虚拟 DOM

  1. 本质就是一个 Object 类型的对象
  2. 虚拟 DOM 比较轻,真实 DOM 比较重。因为虚拟 DOM 是在 React 内部使用,不需要真实 DOM 上那么多的属性
  3. 虚拟 DOM 最终会被 React 编译为真实 DOM,呈现在页面上

JSX 语法 (javascript XML 的简写)

JSX 是 js 的语法扩展,用来描述 UI;在编译之后,JSX表达式会被转换为普通的 js 函数,并且对其取值后得到 js 对象

语法规则:

  1. 定义虚拟DOM时,不能写引号,写引号就成了字符串
  2. JSX 也是一个表达式,这意味着:
    1. 在 if 或者 for 中使用 JSX
    2. 将 JSX 赋值给一个变量
    3. 把 JSX 做为参数传入
    4. 从函数中返回 JSX
  3. 标签中混入JS表达式时要用 {};指定属性时使用引号(单引号或双引号)则为字符串,如果使用大括号,则表明插入的是一个js表达式
  4. 样式的类名指定不要用 class,要用 className,因为 class 是 es6 的关键字
  5. 内联样式要用 style={{key: value}} 的形式去写;且 css 属性以 - 连接的要转换为小驼峰式写法
  6. 虚拟 DOM 必须只能有一个根标签
  7. 标签必须闭合
  8. 标签首字母
    1. 若小写字母开头,则将该标签转为html中同名的标签,若html中无该标签对应的同名元素,则报错
    2. 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错

表达式和语句的区别

表达式会产生一个值,可以放在任何一个需要值的地方,可以使用变量接收;语句就是一段代码

模块,组件,模块化,组件化

模块:向外提供特定功能的 js 程序,一般就是一个 js 程序;模块的作用是复用 js

组件:相对于界面来说,用来实现局部功能效果的代码和资源的集合(html/css/js/images);将 UI 拆分为独立可复用的代码片段,并对每个片段进行构思

react 组件

定义组件的两种方式:函数式组件和类式组件

组件名必须首字母大写,否则会被当成 HTML 标签

函数式组件

函数式组件:接收唯一带有数据的 props(代表属性),并且返回一个 React 元素

函数式组件适用于无状态的简单组件

注意点:

  1. 函数式组件中 this 为 undefined,因为由 babel 转移后的函数开启了严格模式
  2. 函数组件接收唯一参数 props,但不能使用 staterefs
function Demo() {
  // 此处的this是undefined,因为babel翻译过后,开启了严格模式
  console.log(this)
  return <h2>我是用函数定义的组件,示用于【简单组件】的定义</h2>
}

类式组件 class

顾名思义,就是使用类 class 来定义的组件

class 组件适用于有状态的复杂组件

当React元素为用户自定义组件时,它会将JSX所接受的属性以及子组件(children)转换为单个对象传递给组件,这个对象称之为props

注意点:

  1. 必须继承自 React.Component 或者 React.PureComponent
  2. 组件名必须首字母大写,因为首字母小写的会被当成是原生 DOM 标签
  3. 必须重写 render 函数,且 render 函数必须有返回值(返回值可以是 jsx
  4. 所有 React 组件都必须像纯函数1一样保护它们的 props 不被修改
class MyComponent extends React.Component {
  // 构造函数 constructor 只会在初始化组件时执行一遍
  constructor(props) {
    // 在使用 this 之前必须调用 super()
    super(props)
  }
  // render 函数会在初始化组件时执行一遍;且在每次状态发生改变时都会重新执行
  render() {
    // render 函数内的 this 指向当前组件实例
    console.log(this)
    // render 必须要有返回值,如无返回值则会报错
    return <h2>我是用类式组件,示用于【复杂组件】的定义</h2>
  }
}

组件实例的三大属性

组件实例上有三大属性:statepropsrefs

注意:函数组件 无实例,所有没有此属性

组件的状态 state

组件的 state 是组件的状态,完全受控于组件;

组件被称为状态机,通过更新组件的 state 来更新页面对应的页面显示(重新渲染组件)

定义 state 的两种方式:

// 第一种方式,在 constructor 体内,重新定义 state
class MyComponet extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isHot: false
    }
  }
  render() {
      return (<div></div>)
  }
}

// 第二种方式,直接在类内部定义 state
class MyComponet01 extends React.Component {
  state = {
    isHot: false
  }

  // 实际工作中一般会将函数定义为 箭头函数,好处是不用重新定义 this,且可直接使用
  changeIsHot = () => {
    const { isHot } = this.state
    this.setState({
      isHot: !isHot
    })
  }
  render() {
      return (<div></div>)
  }
}
使用 setState 更改状态

当我们定义了 state 之后,可使用 setState 方法来更改 state

使用的两种方式:

  1. 对象式的 setState: setState(stateChange, [callback])
    1. stateChange 是状态改变对象, 该对象可体现出状态的更改
    2. callback 是可选的回调函数, 它在状态更新完毕, 界面也更新完毕(render 被调用完)后才会被调用
  2. 函数式的 setState: setState(updater, [callback])
    1. updater 是 接收原 state 和 props 两个参数的函数
    2. updater 返回值是 stateChange 对象的函数
    3. callback 是可选函数, 它在状态更新完毕, 界面也更新完毕(render 被调用完)后才会被调用

对象式的 setState 是函数式 setState 的简写方式; 如果状态依赖于原 state, 可使用函数式的 setState; 如果状态不依赖于原 state, 可使用对象式的 setState; 如果需要获取 setState() 执行后的最新状态数据, 需要在第二个参数 callback 中来获取

更改 state 注意事项:

  1. 不能直接更改 state(因为不会触发组件更新),应该使用 setState()

  2. setState() 的更新会被合并,当 setState() 时,React 会将对象合并到当前 state

  3. state 的更新是异步更新的,所以不能依赖它们的值来更新下一个状态。想解决 setState() 异步更新的问题,可以让 setState() 接收一个函数而不是一个对象,这个函数用上一个 state 做为第一个参数,将此次更新被应用时的 props 做为第二个参数

    this.setState((state, props) => {})
    
  4. 如果调用多次 setState 来更改同个状态,将会合并为一个,且第一次的生效;处于性能考虑,React 可能会把多个 setState 合并为一个调用

更改状态的两种方式:

// 第一种,在 constructor 内改变方法的 this 指向
class Demo extends React.Component {
  constructor(props) {
    // super(props) 必须在使用 this 之前被调用
    super(props)
    this.state = {
      isHot: true
    }

    // 在constructor内部可改变方法的 this 指向
    this.changeWeather = this.changeWeather.bind(this)
  }

  changeWeather() {
    // 默认这里的 this 为 undefined,
    // 因为此方法是做为事件的,并不是由组件实例来调用的;且 class 内方法内部默认开启了严格模式
    this.setState({
      isHot: !this.state.isHot
    })
  }

  render() {
    const { isHot } = this.state
    return (
      <div>
        <h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
        <button onClick={ this.changeWeather }>改变天气</button>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, document.getElementById('root'))

第二种,箭头函数,推荐写法

class Demo extends React.Component {
  state = {
    isHot: true
  }

  changeWeather = () => {
    this.setState({
      isHot: !this.state.isHot
    })
  }

  render() {
    const { isHot } = this.state
    return (
      <div>
        <h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
        <button onClick={ this.changeWeather }>改变天气</button>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, document.getElementById('root'))

setState 异步更新的测试

class Demo extends React.Component {
  state = {
    isHot: true
  }
  changeWeather = () => {
    // 更新了一次
    this.setState({
      isHot: !this.state.isHot
    })
    // 想要更新第二次
    // 这里并不能立即获取到 state 更新后的值,可以看出 setState 是异步更新的
    // this.setState({
    //   isHot: !this.state.isHot
    // })
    // 想要解决 setState 是异步更新且不能立即获取到更新后的值的问题,可以使用回调函数的方式
    this.setState((state, props) => {
      // 这里可获取到上个 state 更新后的值
      console.log('第一次:', state, props)
      return {
        // 我再这里可立即获得 状态更新后的值
        isHot: !state.isHot
      }
    })
    this.setState((state, props) => {
      // 这里可获取到上个 state 更新后的值
      console.log('第二次:', state, props)
      return {
        // 我再这里可立即获得 状态更新后的值
        isHot: !state.isHot
      }
    })
  }
  render() {
    const { isHot } = this.state
    return (
      <div>
        <h2>这天气好 { isHot ? '炎热' : '凉爽' }</h2>
        <button onClick={ this.changeWeather }>改变天气</button>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, document.getElementById('root'))

组件的属性 props

组件的 props 可以接收组件使用方传递过来的参数;组件标签的所有属性都保存在对象实例的 props 属性内

注意点:

  1. 在组件内部 props 是只读的,不能修改 props 的值
基本使用
// 函数组件中接收参数 props,但不能使用 state 和 refs
function Demo(props) {
  return <h1>我是函数式组件</h1>
}
ReactDOM.render(<Demo name="dname" age={18} say={()=>{}} />, document.getElementById('root'))

// 类式组件中使用 this(当前组件实例),可获取组件使用方传递的 props(尤其是 render 函数内)
class MyComponent extends React.Component {
  // 
  constructor(props) {
    super(props)
  }
  render() {
    // 可直接通过 this 来获取组件实例的 props
    console.log(this.props)
    return <h1>我是类式组件</h1>
  }
}
ReactDOM.render(<MyComponent name="mname" age={20} say={()=>{}} />, document.getElementById('root'))
批量传递 props

在 react,JSX 标签中可以直接使用对象展开符来传递属性 props

class Info extends React.Componet {
  render() {
    console.log(this)
    return (
      <ul>
        <li>姓名: {this.props.name}</li>
        <li>年龄: {this.props.age}</li>
        <li>学历: {this.props.edu}</li>
      </ul>
    )
  }
}
const p = {name: 'lishihao', age: 25, edu: '大专'};
ReacDOM.render(
  <Info {...p} />,
  document.getElementById('app')
)
使用 propTypes 给传入的 props 做限制和使用 defaultProps 给传入的 props 设置默认值
  1. 函数组件时,可在函数上添加静态属性 propTypesdefaultProps 来分别给组件做_类型/是否必须的限制_和_属性的默认值_
  2. 类式组件时,可在类上添加静态属性 propTypesdefaultProps 来分别给组件做_类型/是否必须的限制_和_属性的默认值_

示例:

// 首先需要引入 PropTypes 模块

// 函数组件
function Demo(props) {
  return <h1>函数组件:{ `${props.name}, ${props.age},${props.say()}` }</h1>
}
Demo.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number,
  // 注意这里 function,需要写成 func
  say: PropTypes.func
}
// 对不是必须的属性设置默认值
Demo.defualtProps = {
  age: 18,
  say: () => {}
}

// 类式组件
// 对props进行限制
// 1. 引入PropTypes,
// 2. 定义组件
// class MyProps extends React.Component {
//     render() {
//         console.log(this);
//         const {name, age, sex} = this.props;
//         return (
//             <ul>
//                 <li>姓名:{name}</li>
//                 <li>年龄:{age}</li>
//                 <li>性别:{sex}</li>
//             </ul>
//         );
//     }
// }
// MyProps.propTypes = {
//     // 字符串类型并且是必须的
//     name: PropTypes.string.isRequired,
//     sex: PropTypes.string,
//     age: PropTypes.number,
// }
// MyProps.defaultProps = {
//     // 如果未传sex, 则使用默认值
//     sex: '不难不女'
// }

// 简化
class MyProps extends React.Component {
    render() {
        console.log(this);
        const {name, age, sex} = this.props;
        return (
            <ul>
                <li>姓名:{name}</li>
                <li>年龄:{age}</li>
                <li>性别:{sex}</li>
            </ul>
        );
    }

    // 在开发中尽可能将关于组件的一切都写在组件内部
    static propTypes = {
        // 字符串类型并且是必须的
        name: PropTypes.string.isRequired,
        sex: PropTypes.string,
        age: PropTypes.number,
    }
    static defaultProps = {
        // 如果未传sex, 则使用默认值
        sex: '不难不女'
    }
}
const per = {name: 'lishihoa', age: 12, sex: '男'}
ReactDOM.render(<MyProps {...per}/>, '容器');
构造器函数以及构造器函数中的 props

对于类式组件来说,构造器函数 constructor(),会在组件挂载之前,创建组件实例时被调用。

构造器函数一般两个作用(如果不需要以下,则可以不写 constructor):

  1. 初始化 state
  2. 为事件处理函数绑定实例

如果在构造器函数内(constructor)在使用 this 之前,必须先调用 super();调用 super() 时需要传入 props,也就是执行 super(props) 如果不传入 props,则在之后(构造器函数之中)使用 this.props 可能为 undefined

其他注意事项:

  1. constructor() 函数中不要调用 setState() 方法;如果组件需要内部 state,可直接赋值给 state,也就是说只能在构造器函数中直接为 this.state 赋值
  2. 避免将 props 的值复制给 state(第一毫无必要,第二还会产生bug:更新prop中的值时不会影响到state)

组件的属性 refs

Refs 可以允许我们访问 DOM 元素或在 render 方法中创建的 React 元素;比如管理焦点,文本选择,媒体播放和触发强制动画都可使用 refs

注意:不能在函数组件上使用 ref,因为函数组件没有实例

创建 ref 的三种方式:

  1. 字符串 ref,(不推荐,有性能问题)
  2. 回调函数 ref
  3. React.createRef() 方式
字符串 ref(已弃用,不推荐)

顾名思义,就是直接在 jsx 元素或者 DOM 元素上添加 ref 属性,且 ref 的属性值为一个字符串

字符串 ref 会引发一些效率上的问题,具体问题请参阅:react.docschina.org/docs/refs-a…

class MyComponent extends React.Component {
  // 写成箭头函数的好处是,不需要再为函数 this 绑定当前组件实例
  onBtnClick = (e) => {
    console.log(this)
    // 第二步:直接使用 this.refs 就可以获取字符串 ref
    console.log(this.refs.inputRef)
  }
  render() {
    return (
      <div>
        { /* 第一步:添加 ref,值为一个字符串 */ }
        <input ref="inputRef" placeholder="点击右侧按钮获取我的值" />
        <button onClick={this.onBtnClick}>点击获取值</button>
      </div>
    )
  }
}
ReactDOM.render(<MyComponent />, document.getElementById('root'))
回调 ref(推荐)

顾名思义,ref 值为一个回调函数,此回调函数的参数就是想要获取的元素

内联函数

内联回调函数会有个问题,就是在初始化时执行一次,之后每次元素更新时会被调用两次:第一次参数为 null,第二次才是实际的想要获取的元素。这是因为在每次渲染时都会创建一个新的函数实例,所以 React 清空旧的 ref 并设置新的;通过将 ref 的回调函数定义成 class 的绑定函数的方式可解决此问题

class Demo01 extends React.Component {
  state = {
    count: 0
  }
  onBtnClick = (e) => {
    const { count } = this.state
    console.log('更新前count: ', count, this.currentNode, this.currentNode.value)
    this.setState({
      count: count+1
    })
  }
  inputRefCb = (curNode) => {
    console.log('inputRefCb: ', curNode)
    this.currentNode = curNode
  }
  render() {
    const { count } = this.state
    return (
      <div>
        <input type="text" placeholder="请输入值" ref={this.inputRefCb} value={this.state.count} readOnly/>
        <button onClick={this.onBtnClick}>更新 count: {count}</button>
      </div>
    )
  }
}
ReactDOM.render(<Demo01 />, document.getElementById('root02'))
React.createRef()

使用步骤:

  1. 使用 React.createRef() 来创建一个 ref 容器
  2. ref 属性附加到 React 元素
  3. 当 ref 被传递给 render 中的元素时,对该节点的引用可以在 ref 容器的 current 属性中访问到

ref 的值:

  1. 当 ref 属性用于 html 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层的 DOM 元素做为其 current 属性
  2. 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂在实例做为其 current 属性
  3. 不能在函数组件上使用 ref 属性,因为函数组件没有实例
class Demo extends React.Component {
  // 第一步,使用 React.createRef() 创建一个 ref 容器
  inputRef = React.createRef()

  onBtnClick = (e) => {
    // 第三步,使用 this.inputRef.current 来获取想要的元素
    console.log('按钮点击后,输入框的值为:', this, this.inputRef, this.inputRef.current.value)
  }
  render() {
    return (
      <div>
        { /* 第二步,在想要获取的元素上写上 ref 属性,属性值为 React.createRef 创建的容器 */ }
        <input type="text" placeholder="请输入值" ref={this.inputRef} />
        <button onClick={this.onBtnClick}>获取输入框的值</button>
      </div>
    )
  }
}
ReactDOM.render(<Demo />, document.getElementById('root'))

组件的生命周期

组件在创建,更新,卸载过程中会执行一些特定的方法,这些方法就是生命周期方法,我们可以重写这些方法来在特定的阶段执行一些自定义的功能,比如在组件挂载后开启定时器,ajax 请求、在组件卸载时清空定时器等

React-基础总结

class Count extends React.Component {
    constructor(props) {
        super(props)
        console.log('Count---constructor')
    }

    state = {
        count: 0,
        other: '其他属性'
    }

    /**
     * 必须:
     * 1. static 方法
     * 2. 必须有返回值,返回值是 state 对象或者为 null
     * 
     * 常用于 state 的值在任何时候都取决于 props
     */
    static getDerivedStateFromProps(props, state) {
        console.log('Count---getDirevedStateFromProps');
        return null
        // return props
    }

    componentDidMount() {
        console.log('Count---componentDidMount');
    }

    shouldComponentUpdate() {
        console.log('Count---shouldComponentUpdate');
        return true
    }

    /*
        getSnapshotBeforeUpdate 在最近一次渲染输出(提交到 DOM 节点)之前调用,
        它使得组件能在发生更改之前从 DOM 中获取一些信息(滚动位置等),
        此生命周期的任何返回值都将作为参数(第三个参数)传递给 componentDidUpdate() 
    */
    getSnapshotBeforeUpdate(prevProps, prevState) {
        console.log('Count---getSnapshotBeforeUpdate');
        // return null

        // 在更新前使用快照,返回值作为copmonentDidUpdate 的第三个参数
        return 'lsh'
    }

    componentDidUpdate(preProps, preState, snapshotValue) {
        console.log('Count---componentDidUpdate', preProps, preState, snapshotValue);
    }

    handleAdd = () => {
        console.log('this.state:', this.state)
        this.setState({
            count: this.state.count+1
        })
    }

    render() {
        console.log('Count---render', this);
        return (
            <div>
                <span>当前值为{this.state.count}</span>  
                <button onClick={this.handleAdd}>点击加1</button>  
            </div>
        )
    }
}

事件处理

  1. React 中的事件的命名采用小驼峰式,而不是纯小写;比如 onclick 在 react 中是 onClick
  2. 使用 JSX 语法时,需要传入一个函数做为事件处理函数,而不是一个字符串

注意点:

  1. 不能通过 return false 的方式来阻止默认事件,而必须显式调用 preventDefault
  2. React 中的事件对象是一个 合成事件(SyntheticEvent);合成事件是浏览器原生事件的跨浏览器包装器,除了兼容所有浏览器外,还拥有和浏览器原生事件相同的接口,包括 stopPropagation()preventDefault(),可使用 nativeEvent 属性来获取原生事件

给事件函数传参数:

  1. 使用箭头函数
    <button onClick={(e) => this.handleClick(params, e)}>点击传参</button>
    
  2. 借用 Function.prototype.bind() 函数
    // 事件对象 e 会被默认做为最后一个参数传进去
    <button onClick={this.handleClick.bind(this, params)}>点击传参</button>
    
  3. 使用函数柯里化方式
    class Demo extends React.Component {
      handleClick = (params) => {
        return function(e) {
          // 实际的回调函数处理
          console.log(params, e)
        }
      }
      render() {
        // 意思是将 handleClick 的函数调用结果做为事件处理函数
        return <button onClick={this.handleClick(params)}>点击传参</button>
      }
    }    
    

表单数据

在 HTML 中,表单元素(input,textarea,select)通常维护自己的 state,并根据用户输入进行更新,而在 React 中,可变状态(mutable state)通常保存在组件的 state 中,并只能通过 setState 来更新

两种类型:

  1. 受控组件
  2. 非受控组件

受控组件

使 React 的 state 称为唯一数据源,渲染表单的 React 组件还控制着用户输入过程中表单发生的操作,被 React 以这种方式控制取值的 表单输入元素 就叫做 受控组件

class Demo extends React.Component{
  state = {
    username: '',
    password: ''
  }

  onChange = (dataType) => {
    return (e) => {
      this.setState({
        [dataType]: e.target.value
      })
    }
  }

  handleClick = () => {
    const { username, password } = this.state
    console.log(`提交了:${username}${password}`)
  }

  render() {
    return (
      <div>
        <input type="text" value={this.state.username} onChange={this.onChange('username')} />
        <input type="text" value={this.state.password} onChange={this.onChange('password')} />
        <button onClick={this.handleClick}>点击提交</button>
        {/* 如果将,value 的值设置为 null 或者 undefined 则还是可输入;但如果是固定值,则会阻止用户输入
          <input type="text" value="hi" />
        */}
      </div>
    )
  }
}

非受控组件

在一个受控组件中,表单数据是由 React 组件来管理的;另一种方案是使用 非受控组件,这时表单数据将由 DOM 节点来处理

要编写一个非受控组件,不需要为每个状态更新都编写数据处理函数,可以 使用 ref 从 DOM 节点中获取表单数据

非受控组件,可赋默认值:<input type="checkbox" /><input type="radio" /> 支持 defaultChecked<select><textarea><input type="text" /> 支持 defaultValue

class Demo extends React.Component {

  textRef = React.createRef()

  onSubmit = (e) => {
    // 不能使用 return false 阻止默认事件的提交
    // return false;
    // 只能使用 e.preventDefault() 阻止默认事件的提交
    e.preventDefault()
    // 提交事件
    console.log('onSubmit: ', e, this.textRef, this.hobbyRef)
  }

  render() {
    return (
      <div>
        <form action="#" onSubmit={this.onSubmit}>
          <label>
            {/* 可使用 defaultValue 来给 input 指定默认值 */}
            Name: <input type="text" defaultValue="123456" ref={this.textRef} name="user" />
          </label>

          Hobby:
          <label><input type="checkbox" defaultChecked name="hobby" value="1" />篮球</label>
          <label><input type="checkbox" name="hobby" value="2" />足球</label>
          <label><input type="checkbox" name="hobby" value="3" />乒乓球</label>
          
          <input type="submit" value="Submit" />
        </form>
      </div>
    )
  }
}

组件懒加载(lazyLoad)

一般用于路由组件

使用方式:

  1. 通过 react 的 lazyLoad 函数配合 import() 函数动态加载路由组件: const Home = lazy(() => import('@/pages/Home'))

  2. 通过 <Suspense> 指定在加载到路由打包文件显示前显示的一个自定义 loading 界面

    <Suspense fallback={\<h2\>loading...\</h2\>}>
        <Switch>
            <Route path="/xxx" component={xxx} />
            <Redirect to="/home" />
        </Switch>
    </Suspense>
    

学习视频链接

  1. 千锋 react:www.bilibili.com/video/BV1dP…
  2. 禹神(非常细):www.bilibili.com/video/BV1wy…
  3. 珠峰 react (细又深):www.bilibili.com/video/BV1sx…

Footnotes

  1. 纯函数即不会更改入参,并且多次调用相同的入参都会返回相同的结果