React-基础知识

2021/1/8 React
// 兼容性
React:IE9 及以上
----------------------------------------------------------------------------------------------
// 简介
1. React Fiber // react.js 16 之后的版本,在底层实现循环的时候加入了优先级的概念
2. 声明式开发:减少大量操作 DOM 的代码量
3. 可以与其他框架并存:只管理如 id = root 的节点
4. 组件化
5. 单向数据流:子组件只能使用父组件传递过来的值而不能去修改值
6. 视图层框架:react 在处理大型项目时不够用,只能用它搭建视图
7. 函数式编程:更容易实现前端自动化测试
8. 脚手架
  create-react-app 项目名称
  create-react-app 项目名称 --template typescript
----------------------------------------------------------------------------------------------
// React Fiber 如何性能优化
1. 在组件 patch 的时候会被拆分为两个阶段
  1. reconciliation 阶段 // 执行 diff 算法,纯 js 计算
  2. commit 阶段 // 将 diff 算法结果渲染 DOM
2. 因为 js 是单线程,且和 DOM 渲染共用一个线程,当组件足够复杂,组件更新时计算和渲染的压力大
   同时再有 DOM 操作需求(动画、鼠标拖拽等)就会卡顿,如何解决?
    1. 将 reconciliation 阶段进行任务拆分 // commit 阶段无法拆分,涉及到浏览器
    2. DOM 需要渲染时暂停,空闲时恢复 // react-fiber
    3. 通过 window.requestIdleCallback 这个 API 能够知道什么时候空闲
----------------------------------------------------------------------------------------------
// 事件绑定
1. 什么情况下需要bind(this)1. onClick传入的事件处理函数是普通函数的时候,需要bind(this)来改变指向
  2. 可以在 JSX 模板中去写 bind,也可以在 constructor 写 bind // 建议写在 constructor,否则写在模板中会导致数据变化一次执行一次 bind 函数
  3. 如果在 JSX 模板中写 bind 可以传递其他参数,如果在 constructor 写 bind 此时是不能传递参数的
2. 为什么要用bind(this)1. 如果不用bind(this), this会指向undefined
3. 可以不用bind(this)吗?
  1.JSX 模板中用箭头函数形式绑定如 onClick = { e => { this.handleClick('name', e) }}
  2.JSX 模板中直接绑定事件处理函数名称,在函数定义的时候使用箭头函数,如 handleClick = () => {}
  3. 如果不涉及到传参建议不要使用第 1 种方式,这样相当于是执行了一个回调函数
  4. 如果涉及到多个参数的传递,本身的事件对象 event 需要在接收的时候写在最后一个
  5. 如果要传递其他参数则需要使用第 1 种方式,或直接在 JSX 中绑定 this 传递
4. 事件参数 event 是 react 封装过的 syntheticEvent 组合事件,不是原生的 event,它模拟了 DOM 事件的所有能力
5. event.nativeEvent 是原生事件对象
6. 所有事件,都被挂载到 document 上 // 在 react 17 之后,事件被挂载到 root div 上,这样有利于多个 react 并存
7.DOM 事件不一样,和 vue 事件也不一样
8. 合成事件 syntheticEvent
  1. 更好的兼容性和跨平台
  2. 挂载到 document,减少内存消耗,避免频繁解绑
  3. 方便事件的统一管理(如事物机制)
  4. 通过事件冒泡到 document 上
  5. 在 react 17 之后,事件被挂载到 root div 上,这样有利于多个 react 并存
----------------------------------------------------------------------------------------------
// 什么是 JSX
1. JSX 即 JavaScript XML,是一种在 React 组件内部构建标签的类 XML 语法。
2. JSX 并不是一门新的语言,仅仅是个语法糖(syntactic sugar),允许开发者在 JavaScript 中书写 HTML 语法。
   最后,每个 HTML 标签都转化为 JavaScript 代码来运行。这样对于使用 JavaScript 来构建组件以及组件之间关系的应用
   在代码层面显得更加清晰。而不再像过于一样用 JavsScript 操作 DOM 来创建组件以及组件之间的嵌套关系。
3. JSX 通过 React.createElement 函数创建返回 vnode,再经过 patch 生成新的 vdom
4. 自己定义的组件名开头一般是大写字母
5. 示例父组件 TodoList
  import React, { Component, Fragment } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
  import TodoItem from './TodoItem'
  import './style.css' // 直接引入 css 文件,在标签上使用 className 绑定类名
  class TodoList extends Component {
    constructor(props) {
      super(props)
      this.state = { // 数据存储的地方,相当于 vue 的 data
        inputValue: '',
        list: []
      }
      // 在上面绑定了 this,在 JSX 中就不用 bind 了
      // 写在这里避免了 render 函数重复执行而重新 bind
      this.handleInputChange = this.handleInputChange.bind(this)
      this.handleBtnClick = this.handleBtnClick.bind(this)
      this.handleItemDelete = this.handleItemDelete.bind(this)
    }
    render() {
      // 实际 return 没有 `` 反引号,只为了编辑器展示效果!!!
      return (`
        // Fragment 组件是 react 提供的相当于 block 标签的功能,不会再 DOM 中显示,只做一个包裹作用
        // 在 JSX 中绑定变量需要用花括号括起来,绑定事件时需要用 bind 函数改变 this 作用域,事件名称的第二个单词首字母是大写的,如 onChange
        <Fragment>
          <div>
            {/* 在 JSX 语法中写注释需要这样写 */}
            {
              // 也可以这样写
            }
            <label htmlFor = "inputId">
            {/* label 有个 for 属性用来结合 input 的 id 做扩大点击范围,但是 for 会跟 js 的 for 冲突,所以需要写成 htmlFor */}
            <input id = "inputId" className = "css" value = { this.state.inputValue } onChange = { this.handleInputChange.bind(this) }>
            <button onClick = { this.handleBtnClick.bind(this) }>提交</button>
          </div>
          <ul>
            { this.handleInitHtml() } {/* 通过方法执行和以下代码效果一样 */}
            {
              this.state.list.map((item, index) => {
                return (
                  <div key = { index }>
                    {/* 和 vue 差不多都是通过属性方式传值,但是 react 可以传递方法让子组件调用,记得传递方法时要 bind this */}
                    <TodoItem content = { item } index = { index } deleteItem = { this.handleItemDelete.bind(this) } />
                    {/* 以下是未拆分组件的写法,上面是拆分组件的写法 */}
                    <li
                      key = { index }
                      onClick = { this.handleItemDelete.bind(this, index) }
                      dangerouslySetInnerHTML = {{__html: item}}
                    >
                      {/* 正常直接写 { item },这样不会转义 html 标签,可以用 dangerouslySetInnerHTML 属性转义包含 html 标签的内容 */}
                      {/* 注意 xss 攻击,这个不会自动做转换标签,插值表达式会自动转换 */}
                    </li>
                  </div>
                )
              })
            }
          </ul>
        </Fragment>`
      )
    }
    handleInitHtml() {
      return this.state.list.map((item, index) => {
        return (
          <div key = { index } style = {{ backgroundColor: 'red', width: '500px' }}>
            {/* 和 vue 差不多都是通过属性方式传值,但是 react 可以传递方法让子组件调用,记得传递方法时要 bind this */}
            <TodoItem content = { item } index = { index } deleteItem = { this.handleItemDelete.bind(this) } />
            {/* 以下是未拆分组件的写法,上面是拆分组件的写法 */}
            <li
              key = { index }
              onClick = { this.handleItemDelete.bind(this, index) }
              dangerouslySetInnerHTML = {{__html: item}}
            >
              {/* 正常直接写 { item },这样不会转义 html 标签,可以用 dangerouslySetInnerHTML 属性转义包含 html 标签的内容 */}
              {/* 注意 xss 攻击,这个不会自动做转换标签,插值表达式会自动转换 */}
            </li>
          </div>
        )
      })
    }
    handleInputChange(e) {
      // 改变 state 中的数据必须使用 setState
      this.setState({
        inputValue: e.target.value
      })
      // 在官网 react 中推荐使用传入函数而不是对象
      const value = e.target.value // 必须在外面定义,否则报错,因为 SyntheticEvent 包装器
      this.setState(() => {
        return {
          inputValue: value
        }
      })
      // 上面的写法可以写成 es6 的简写
      const value = e.target.value // 必须在外面定义,否则报错,因为 SyntheticEvent 包装器
      this.setState(() => ({
        inputValue: value
      }))
    }
    handleBtnClick() {
      this.setState({
        list: [...this.state.list, this.state.inputValue],
        inputValue: ''
      })
      // 在官网 react 中推荐使用传入函数而不是对象,接收的参数第一个是改变之前的 state,第二个是 props
      this.setState((prevState, props) => ({
        list: [...prevState.list, prevState.inputValue],
        inputValue: ''
      }))
    }
    handleItemDelete(index) {
      // 在 react 中有 immutable 概念,就是 state 不允许我们直接去修改,需要拷贝成副本去操作
      const list = [...this.state.list]
      list.splice(index, 1)
      this.setState({
        list
      })
      // 在官网 react 中推荐使用传入函数而不是对象,接收的参数第一个是改变之前的 state,第二个是 props
      this.setState((prevState, props) => {
        const list = [...prevState.list]
        list.splice(index, 1)
        return {
          list
        }
      })
    }
  }
  export default TodoList
6. 示例子组件 TodoItem
  import React, { Component } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
  class TodoItem extends Component {
    constructor(props) {
      super(props)
      this.handleClick = this.handleClick.bind(this) // 在这里做了 bind 就不用在绑定的时候去 bind
    }
    render() {
      const { content } = this.props
      return (
        <div onClick = { this.handleClick }> {/* 因为在 constructor 中已经 bind 了 this 所以在这里不用在 bind */}
          { this.props.content } {/* 直接通过 this.props 获取传递过来的属性值 */}
          { content } {/* 也可以通过解构 */}
        </div>
      )
    }
    handleClick() {
      {/* 直接通过 this.props 获取传递过来的方法 */}
      this.props.deleteItem(this.props.index)
      {/* 以下通过解构等价于上面 */}
      const { deleteItem, index } = this.props
      deleteItem(index)
    }
  }
  export default TodoItem
----------------------------------------------------------------------------------------------
// PropTypes 与 DefaultProps 校验 props 参数
import React, { Component } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
import PropTypes from 'prop-types' // 不需要安装,脚手架自带
  class TodoItem extends Component {
    render() {
      const {
        content1,
        content2,
        content3,
        content4,
        content5,
        content6,
        content7,
        content8,
        content9,
      } = this.props
      return (
        <div>
          { content1 }
          { content2 }
          { content3 }
          { content4 }
          { content5 }
          { content6 }
          { content7 }
          { content8 }
          { content9 }
        </div>
      )
    }
  }
TodoItem.propTypes = {
  // isRequired 必填的意思
  // oneOfType 相当于 或 操作
  content1: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
  content2: PropTypes.number.isRequired,
  content3: PropTypes.bool.isRequired,
  content4: PropTypes.symbol.isRequired,
  content5: PropTypes.array.isRequired,
  content6: PropTypes.object.isRequired,
  content7: PropTypes.func.isRequired, // 函数
  content8: PropTypes.node.isRequired, // 节点
  content9: PropTypes.element.isRequired, // react 元素
}
TodoItem.defaultProps = {
  content1: '默认值1',
  content2: '默认值2',
  content3: '默认值3',
  content4: '默认值4',
  content5: '默认值5',
  content6: '默认值6',
  content7: '默认值7',
  content8: '默认值8',
  content9: '默认值9',
}
export default TodoItem
----------------------------------------------------------------------------------------------
// 插槽
1.  this.props.children
----------------------------------------------------------------------------------------------
// props、state、render 之间的关系
1. 当组件的 state 或 props 发生改变的时候,render 函数就会重新执行
2. 当父组件的 render 函数被运行时,它的子组件的 render 都将被重新运行一次
----------------------------------------------------------------------------------------------
// 什么是虚拟 DOM
1. 正常流程
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成真实的 DOM 来显示
  4. state 发生改变
  5. 数据 + 模板结合,生成真实的 DOM 来替换原始的 DOM
  6. 缺陷
    1. 第一次生成了一个完整的 DOM 片段
    2. 第二次生成了一个完整的 DOM 片段
    3. 第二次的 DOM 替换第一次的 DOM 非常消耗性能
2. 优化流程
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成真实的 DOM 来显示
  4. state 发生改变
  5. 数据 + 模板结合,生成真实的 DOM 并不直接替换原始的 DOM
  6. 新的 DOM(Document Fragment)和原始的 DOM 做比对,找差异
  7. 找出有变化的地方
  8. 只用新的 DOM 中有变化的地方替换老的 DOM 有变化的地方
  9. 缺陷
    1. 性能的提升不明显
3. 深度优化流程
  1. state 数据
  2. JSX 模板
  3. 数据 + 模板结合,生成虚拟 DOM(虚拟 DOM 就是一个 JS 对象,用它来描述真实的 DOM)(损耗了性能)
  4. 用虚拟 DOM 的结构生成真实的 DOM 来显示
  5. state 发生改变
  6. 数据 + 模板生成新的虚拟 DOM(极大提升了性能)
  7. 比较原始虚拟 DOM 和新的虚拟 DOM 的区别 // diff 算法
  8. 只用新的 DOM 中有变化的地方替换老的 DOM 有变化的地方
  9. 优点
    1. 性能提升了
    2. 它使得跨端应用得以实现!!!
----------------------------------------------------------------------------------------------
// 深入了解虚拟 DOM
1. JSX ——> React.createElement(render) ——> 虚拟 DOMJS 对象)——> 真实 DOM
2. JSX 模板会通过 render 函数转化成虚拟 DOM 然后再生成真实 DOM
----------------------------------------------------------------------------------------------
// 虚拟 DOM 中的 Diff 算法
1. 和 vue 的 diff 算法差不多,都是同级比较,如果有一级不匹配,则不会再进行子级的比较,会直接重新生成新的 DOM
2. 之所以 setState 是异步的就是可以把短时间内多次修改合并成一次修改,减少 diff 的比较
3. for 循环中的 key 值就是为了在 diff 做对比的时候有参照,如果没有 key 则无法做对比,如果 key 值是随机数或下标会导致无法充分利用 DOM
----------------------------------------------------------------------------------------------
// react 组件渲染过程
1. props、state
2. render 生成 vnode
3. patch(ele, vnode)
----------------------------------------------------------------------------------------------
// react 组件更新过程
1. setState(newState) ——> dirtyComponents(可能有子组件,不只当前组件)
2. render 生成 newVnode
3. patch(ele, newVnode)
----------------------------------------------------------------------------------------------
// react 中 ref 的使用
1. 用于获取 DOM 节点或组件引用
2. 因为 setState 是异步的,如果想要在改变完数据就马上去获取 DOM 节点,有时候就会出错
3. 可以在 setState 方法中传入第二个参数,它是一个函数,写在函数体中就可以获取到更新完数据的 DOM 节点
4. 示例
import React, { Component } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
  class TodoItem extends Component {
    constructor(props) {
      super(props)
      this.state = { // 数据存储的地方,相当于 vue 的 data
        count: 0,
      }
      this.input = React.createRef() // 创建 ref
    }
    render() {
      const { content } = this.props
      return (
        <input ref = { input => this.input = input } onChange = { this.handleChange.bind(this) } />
        {/* ref 使用方式上下都可以 */}
        <input ref = { this.input } onChange = { this.handleChange.bind(this) } />
      )
    }
    handleChange(e) {
      this.setState(state => {
        return {
          count: state++
        }
      }, () => {
        // 在这里可以获取到更新完数据后的最新的 DOM 节点,相当于 vue 的 nextTick
        console.log(this.input.value === e.target.value)
      })
    }
  }
  export default TodoItem
----------------------------------------------------------------------------------------------
// react 的生命周期
1. 初始化阶段
  1. setup props and state
2. 挂载阶段
  1. componentWillMount // 组件被挂载之前触发
  2. render // 组件渲染时触发
  3. componentDidMount // 组件挂载完成之后触发
3. 更新阶段
  1. state
    1. shouldComponentUpdate // 确认是否更新,返回 true or false
    2. componentWillUpdate // 数据更新之前触发
    3. render // 组件渲染时触发
    4. componentDidUpdate // 数据更新之后触发
  2. props
    1. componentWillReceiveProps // 当子组件重新接收了父组件传递过来的值时触发,即父组件更新了传递的值时,第一次渲染接收不会触发
    2. shouldComponentUpdate // 确认是否更新,返回 true or false
    3. componentWillUpdate // 数据更新之前触发
    4. render // 组件渲染时触发
    5. componentDidUpdate // 数据更新之后触发
4. 卸载阶段
  1. componentWillUnmount // 组件被剔除之前触发
----------------------------------------------------------------------------------------------
// react 的生命周期使用场景
1. 在 React.Component 这个组件中有默认了除了 render 这个生命周期函数外的所有其他的生命周期函数,所以在组件中 render 函数不可省略
2. 父组件的 render 函数被执行时,子组件的 render 函数也会被重新执行
3. 针对比第 2 点的问题可以在子组件中的 shouldComponentUpdate 这个生命周期函数中做一些判断如:
  shouldComponentUpdate(nextProps, nextState) {
    if (nextProps.content !== this.props.content) {
      return true
    } else {
      return false
    }
  }
4. 从第 3 点中我们也可以在父组件中通过 nextState 这个参数来控制我们的数据是否需要去重新更新进行判断
5. 如果需要发送 ajax 建议放在 componentDidMount 这个生命周期函数中
6. shouldComponentUpdate 是性能优化的重点!!! // 需要进行优化才优化,为了业务或需求开发,而不是为了技术开发
7. react 的 shouldComponentUpdate 为什么不帮助我们写好数据有变化才渲染,没变化就不渲染?
  1. 因为 setState 的不可变值,如果开发者使用了 push、pop 等方法去改变数据,就会导致 state 一直一样
     这样组件就会一直不会重新渲染,react 规定不了开发者的编码,所以开放了这个 shouldComponentUpdate
----------------------------------------------------------------------------------------------
// React.PureComponent 的使用
1. 因为使用了 react-redux 进行 store 的连接,当 store 中的数据发生变化时,每个连接的组件都会知道,都要重新执行 render
   有些数据的改变可能跟其他组件没关系,所以需要使用生命周期 shouldComponentUpdate 判断是否需要进行 render
   但是如果每个组件都需要这么做就太麻烦了,react 提供了一个 PureComponent 组件帮助我们做这些事
   前提是使用了 immutable 管理 store 中的数据,否则直接使用 PureComponent 会遇到坑,需要自己手写判断 shouldComponentUpdate
2. 实际上使用了 PureComponent 的组件,它的 shouldComponentUpdate 会进行浅比较,而不是深比较
3. 示例 // 把 Component 替换成 PureComponent 即可
  import React, { PureComponent } from 'react'
  class App extends PureComponent {}
4. React.memo 和 React.PureComponent 差不多,一个用于无状态组件(memo),一个用于普通组件(PureComponent)
  const Component = props => {}
  const areEqual = (prevProps, nextProps) => {
    /* 如果把 nextProps 传入 render 方法的返回结果和把 prevProps 传入 render 方法
       的返回结果一致则返回 true,否则返回 false
    */
  }
  export default React.meno(Component, areEqual)
----------------------------------------------------------------------------------------------
// react 的动画
1. css 过渡动画
  1. 通过动态切换类名实现,动态绑定和 vue 类似
  2. 和正常 css 一样直接使用 transition
2. keyframes 动画
  1. 通过动态切换类名实现,动态绑定和 vue 类似
  2. 和正常 css 一样直接使用 keyframes
3. react 模块 CSSTransition // npm install react-transition-group
   import React, { Component, Fragment } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
   import { CSSTransition } from 'react-transition-group'
   import './style.css'
    class App extends Component {
      constructor(props) {
        super(props)
        this.state = { // 数据存储的地方,相当于 vue 的 data
          show: true,
        }
      }
      render() {
        return (
          <Fragment>
            <CSSTransition
              in = { this.state.show } // 绑定变量
              timeout = { 1000 } // 动画时长
              classNames = 'fase' // 类名前缀
              unmountOnExit // 动画结束移除 DOM
              onEntered = {el => {el.style.color = 'blue'}} // 生命周期
              appear = { true } // 入场一开始就执行动画
            >
              <div>hello</div>
            </CSSTransition>
            <button onClick = { this.handleClick.bind(this) }>切换</button>
          </Fragment>
        )
      }
      handleClick() {
        this.setState((state, props) => {
          show: !state.show
        })
      }
    }
    export default App
    // 以下是 css 文件
    .fade-enter, .fade-appear { // 入场动画执行的第一帧,.fade-appear 表示初始化时就执行动画
      opacity: 0;
    }
    .fade-enter-active, .fade-appear-active { // 入场动画执行时,.fade-appear-active 表示初始化时就执行动画
      opacity: 1;
      transition: opacity 1s ease-in;
    }
    .fade-enter-done { // 入场动画执行完成时
      opacity: 1;
    }
    .fade-exit { //离场动画执行的第一帧
      opacity: 1;
    }
    .fade-exit-active { // 入场动画执行时
      opacity: 0;
      transition: opacity 1s ease-in;
    }
    .fade-exit-done { // 入场动画执行完成时
      opacity: 0;
    }
4. react 模块 TransitionGroup // npm install react-transition-group
   import React, { Component, Fragment } from 'react' // 一定要引入 React 否则编译报错,虽然在代码没显式用到
   import { CSSTransition, TransitionGroup } from 'react-transition-group'
   import './style.css'
    class App extends Component {
      constructor(props) {
        super(props)
        this.state = { // 数据存储的地方,相当于 vue 的 data
          list: [],
        }
      }
      render() {
        return (
          <Fragment>
            <TransitionGroup>
            {
              this.state.list.map((item, index) => {
                return (
                  <CSSTransition
                    timeout = { 1000 } // 动画时长
                    classNames = 'fase' // 类名前缀
                    unmountOnExit // 动画结束移除 DOM
                    onEntered = {el => {el.style.color = 'blue'}} // 生命周期
                    appear = { true } // 入场一开始就执行动画
                    key = { index }
                  >
                    <div>{ item }</div>
                  </CSSTransition>
                )
              })
            }
            </TransitionGroup>
            <button onClick = { this.handleAddItem.bind(this) }>切换</button>
          </Fragment>
        )
      }
      handleClick() {
        this.setState((state, props) => {
          list: [...state.list, 'item']
        })
      }
    }
    export default App
    // 以下是 css 文件
    .fade-enter, .fade-appear { // 入场动画执行的第一帧,.fade-appear 表示初始化时就执行动画
      opacity: 0;
    }
    .fade-enter-active, .fade-appear-active { // 入场动画执行时,.fade-appear-active 表示初始化时就执行动画
      opacity: 1;
      transition: opacity 1s ease-in;
    }
    .fade-enter-done { // 入场动画执行完成时
      opacity: 1;
    }
    .fade-exit { //离场动画执行的第一帧
      opacity: 1;
    }
    .fade-exit-active { // 入场动画执行时
      opacity: 0;
      transition: opacity 1s ease-in;
    }
    .fade-exit-done { // 入场动画执行完成时
      opacity: 0;
    }
----------------------------------------------------------------------------------------------
// setState 不可变值
1. 在对 state 中的数据做修改时,不能直接修改 state 中的数据
  1. 数组 // 不可直接使用 push pop splice 等会影响到原数组的 API
    const list5Copy = this.state.list5.slice()
    list5Copy.splice(2, 0, 'a')
    this.setState({
      list1: this.state.list1.concat(100), // 追加
      list2: [...this.state.list1, 100], // 追加
      list3: this.state.list3.slice(0, 3), // 截取
      list4: this.state.list4.filter(item => item > 100), // 筛选
      list5: list5Copy // 其他操作
    })
  2. 对象 // 不可直接对 obj 进行属性设置
    this.setState({
      obj1: Object.assign({}, this.state.obj1, { a: 100 }),
      obj2: {...this.state.obj2, a: 100}
    })
----------------------------------------------------------------------------------------------
// setState 可能是同步可能是异步
1. 在上面中,我们已经知道,如果直接使用 setState 改变 state 它是异步的,需要在 setState 这个 API 后多传一个函数做参数
   把需要同步操作的代码放在第二个参数中,达到 vue 的 nextTick 的效果
2. 如果 setState 这个方法放在 setTimeout 这个方法中,它是同步的!!!不需要多传第二个参数
3. 当我们自定义事件如 document.body.addEventListener('click', this.handle),在这个事件回调中的 setState 也是同步的!!!
4. batchUpdate 机制 // 同步还是异步
  1. 在 react 组件中的函数执行时,如果有调用了 setState 方法(没有调用也会设置),那么这个函数在执行前 react 内部会
     有一个 isBatchingUpdates flag 被设置为 true,当函数执行完后这个 isBatchingUpdates flag 会被设置成 false
     如果是 true 就会走到 batchUpdate 机制中,就是异步更新,如果是 false,就不会走到,就是同步更新,而上面写到在 setTimeout
     和自定义事件是同步更新的原因就是因为这两个代码是异步执行的,所以函数已经同步执行完了,isBatchingUpdates 已经是 false 了
     自然就不会走到 batchUpdate 机制中,就会走同步更新!!!
  2. 当 isBatchingUpdates falg 是 true 时,会把当前组件保存到 dirtyComponents 中(相当于是异步队列)
     当 isBatchingUpdates 是 false 时,会遍历所有的 dirtyComponents 调用 updateComponent 再更新 pending state or props
  3. 哪些能命中 batchUpdate 机制?
    1. 生命周期(和它调用的函数)
    2. React 中注册的事件(和它调用的函数)
  4. 哪些不能命中 batchUpdate 机制?
    1. setTimeout、setInterval 等(和它调用的函数)
    2. 自定义的 DOM 事件(和它调用的函数)
5. transaction 事物机制
  1. 一个函数执行时,会先执行 react 中定义的特定的开始逻辑,再执行函数,完了会再执行特定的结束逻辑
  2. batchUpdate 机制就是使用 transaction 事物机制实现的
----------------------------------------------------------------------------------------------
// setState 可能会被合并
1. 如果 setState 传入的是对象就会被合并,后面会覆盖前面,如果是函数则不会
  1. 这里连续直接两遍,count 初始值是 0,执行完 count 是 5,而不是 10
    this.setState({
      count: this.state.count + 10
    })
    this.setState({
      count: this.state.count + 5
    })
  2. 这里传入的是函数,连续执行两遍,结果是 2
    this.setState((prveState, props) => {
      return {
        count: prveState.count + 1
      }
    })
    this.setState((prveState, props) => {
      return {
        count: prveState.count + 1
      }
    })
----------------------------------------------------------------------------------------------
// UI 组件和容器组件
1. UI 组件又称傻瓜组件,只负责页面显示,没有其他逻辑
2. 容器组件又称聪明组件,只负责页面的逻辑,不负责页面渲染
----------------------------------------------------------------------------------------------
// 无状态组件或函数组件
1. 无状态组件就是一个函数,参数接收一个 props,函数返回一个 JSX
2. 如果一个 react 组件只有一个 render 的生命周期就可以用无状态组件代替
3. 无状态组件不需要继承 React.Component
4. 简单的 UI 组件就可以用无状态组件表示
----------------------------------------------------------------------------------------------
// react portals 使用场景
1. 组件默认会按照既定的层次渲染嵌套渲染
2. 如何让组件渲染到父组件以外? // react portals
3. react portals 可以让组件渲染到对应的 DOM 节点上,以此可以解决 css 方面的问题如 z-index 层级低、fixed 需要放在 Body 上等
4. 示例
  // 组件而不是 App.js
  import React from 'react'
  import ReactDOM from 'react-dom'

  class TodoList extends Component {
    render() {
      // 第一个参数 JSX,第二个是要渲染到哪个 DOM 节点上
      // this.props.children 相当于 vue 的 slot
      return ReactDOM.createPortal(<div>{ this.props.children}</div>, document.body)
    }
  }
  export default TodoList
----------------------------------------------------------------------------------------------
// react context
1. 公共信息(语言、主题)如何传递给每个组件?
2. 用 props 太繁琐,用 redux 小题大做
3. 相当于 vue 的 provide 和 inject // 爷爷组件和孙子组件通信
4. 示例
  // 爷爷组件
  import React from 'react'
  import erzi from './erzi.js'
  export const ThemeContext = React.createContext('light') // light 只是默认值
  class yeye extends Component {
    constructor(props) {
      super(props)
      this.state = {
        theme: 'light'
      }
    }
    render() {
      return (
        {/* value 就是对 context 的修改 */}
        <ThemeContext.Provider value = { this.state.theme }>
          <erzi />
        </ThemeContext.Provider>
      )
    }
  }
  export default yeye
  // 儿子组件
  import React from 'react'
  import sunzi1 from './sunzi1.js'
  import sunzi2 from './sunzi2.js'
  class erzi extends Component {
    render() {
      return (
        <div>
          <sunzi1 />
          <sunzi2 />
        </div>
      )
    }
  }
  export default erzi
  // class 孙子组件1
  import React from 'react'
  import { ThemeContext } from '爷爷组件'
  class sunzi1 extends Component {
    static contextType = ThemeContext // ThemeContext 就是爷爷组件的定义的,和下面使用方式一样
    render() {
      return (
        <div>{ this.context }</div>
      )
    }
  }
  sunzi1.contextType = ThemeContext // ThemeContext 就是爷爷组件的定义的,和使用 static 方式一样
  export default sunzi1
  // 无状态 孙子组件2
  import React from 'react'
  import { ThemeContext } from '爷爷组件'
  const sunzi2 = props => {
    return (
      {/* ThemeContext 就是爷爷组件的定义的 */}
      {/* 包裹的内容使用箭头函数返回 jsx */}
      <ThemeContext.Consumer>{ value => <p>value</p> }</ThemeContext.Consumer>
    )
  }
  export default sunzi2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
Last Updated: 2024/6/11 14:20:38
    飘向北方
    那吾克热-NW,尤长靖