ReactsetState原理受控及非受控组件——前端之React之二【Day84-Day90】

118 阅读10分钟

挑战坚持学习1024天——前端之JavaScript高级

js基础部分可到我文章专栏去看 ---点击这里

Day91-Day97【2022年11月7日-13日】

学习重点setState原理-性能优化-ref-受控组件、非受控组件-高阶组件-Fragment-过渡动画

1. 非父子组件的通信有哪些方式?分别是什么作用?

  • Context

    • 1.创建Context

    • 2.使用<context.Provider>包裹后代组件

    • 3.在要使用的后代xxxx组件引入context

      • xxxx.contextType = context
      • 在render方法中可以通过this.context拿到传递过来的值
  • 事件总线EventBus

  • Redux

2. 面试题:React的setState是同步的还是异步的?React18中是怎么样的?

  • 在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新

  • React的setState是异步的 -- 不要指望在调用 setState 之后,this.state 会立即映射为新的值

  • 在react18之前, 在setTimeout,Promise等中操作setState, 是同步操作,在生命周期事件函数中是异步(批处理)

  • 在react18之后, 在setTimeout,Promise等中操作setState,是异步操作(批处理)

    • 如果需要同步的处理怎么办呢? 需要执行特殊的flushSync操作
  • 为什么要将setState设计成异步的

    • 首先,若是将setState设计成同步的,在componentDidMount中请求多个网络请求时,会堵塞后面的网络请求
    componentDidMount() {
      // 网络请求一 : this.setState
      // 网络请求二 : this.setState
      // 网络请求三 : this.setState
      // 如果this.setState设计成同步的,会堵塞后面的网络请求
    }
    
    • 一. setState设计为异步,可以显著的提升性能

      • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的
      • 最好的办法应该是获取到多个更新,之后进行批量更新
      // 在一个函数中有多个setState时,
      this.setState({}) --> 先不会更新,而是会加入到队列(queue)中 (先进先出)
      this.setState({}) --> 也加入到队列中
      this.setState({}) --> 也加入到队列中
      // 这里的三个setState会被合并到队列中去
      // 在源码内部是通过do...while从队列中取出依次执行的
      
    • 二: 如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步

3. 性能优化:什么是SCU优化?类组件和函数组件分别如何进行SCU的优化?

  • shouldComponentUpdate -- SCU -- React提供给我们的声明周期方法

  • SCU优化就是 一种巧妙的技术,用来减少DOM操作次数,具体为当React元素没有更新时,不会去调用render()方法

  • 可以通过shouldComponentUpdate来判断this.state中的值是否改变

    shouldComponentUpdate(nextProps, nextState) {
       const {message, counter} = this.state
       if(message !== nextState.message || counter !== nextState.counter) {
         return true
       }
       return false 
     }
    
  • React已经帮我们提供好SCU优化的操作

    • 类组件: 将class继承自PureComponent
    • 函数组件: 使用一个高阶组件memo
    import {mome} from 'react'const HomeFunc = mome(function(props) {
      console.log("函数式组件")
      return (
        <h4>函数式组件: {props.message}</h4>
      )
    })
    ​
    export default HomeFunc
    

4. React为什么要强调不可变的力量?如何实现不可变的力量?

  • 不可变的力量: 不要直接去修改this.state中的值(主要指对象),若是想修改的话,应该是将这整个值全部修改掉

    • 注意: 值类型,在修改的时候,本身就全部替换掉了,所以不需要其他操作,直接改就可以
  • 实现: 将对象浅拷贝赋值给一个新的变量,在将这个新的变量赋值给this.state中的值

5. React中获取DOM的方式有哪些?

  • ref获取DOM

    getDOM() {
    // 方式一: 在react元素上绑定ref字符串 - 这种方式react已经不推荐了
    // console.log(this.refs.http)// 方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素(推荐)
    // console.log(this.titleRef.current)// 方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入(16.3之前的版本)
    // console.log(this.titleEl)
    }
    <h3 ref="http">大大怪将军</h3>
    <h3 ref={this.titleRef}>小小怪下士</h3>
    <h3 ref={el => this.titleEl = el}>瑞克</h3>
    <button onClick={() => this.getDOM()}>获取DOM</button>
    
  • ref获取组件实例 -- createRef

    import React, { PureComponent, createRef } from 'react'constructor() {
        super()
        this.state = {}
        this.HWRef = createRef()
    }
    ​
    getComponent() {
        console.log(this.HWRef.current)
        this.HWRef.current.test()
    }
    ​
    <HelloWorld ref={this.HWRef} />
    <button onClick={() => this.getComponent()}>获取组件实例</button>
    
  • ref获取函数组件 -- 函数式组件是没有实例的,所以无法通过ref获取他们的实例 -- React.forwardRef

    import React, { PureComponent, createRef, forwardRef } from 'react'const HelloWorld = forwardRef(function(props, ref) {
      return (
        <div>
          <h2 ref={ref}>函数组件</h2>
          <h4>大大怪将军</h4>
        </div>
      )
    })
    constructor() {
        super()
        this.state = {}
        this.HWRef = createRef()
    }
    ​
    getComponent() {
        console.log(this.HWRef.current)
    }
    ​
    render() {
        return (
          <div>
            <HelloWorld ref={this.HWRef} />
            <button onClick={() => this.getComponent()}>获取DOM</button>
          </div>
        )
    }
    

6. 性能优化:React的diff算法和key的作用

  • React的渲染流程

    • 在render函数中返回jsx, jsx会创建出ReactElement对象(通过React.createElement的函数创建出来的)
    • ReactElement最终会形成一颗树结构, 这颗树结构就是vDOM
    • React会根据这样的vDOM渲染出真实DOM
  • React更新流程

    • props/state发生改变
    • render函数重新执行
    • 产生新的DOM树结构
    • 新旧DOM树 进行diff算法
    • 计算出差异进行更新
    • 最后更新到真实DOM
    什么是diff算法?
        diff算法并非React独家首创,但是React针对diff算法做了自己的优化,使得时间复杂度优化成了O(n)
    对比两颗树结构,然后帮助我们计算出vDOM中真正变化的部分,并只针对该部分进行实际的DOM操作,而非渲染整个页面,从而保证了每次操作后页面的高效渲染。
    
  • React在props或state发生改变时,会调用React的render方法,会创建一颗不同的树

  • React需要基于这两颗不同的树之间的差别来判断如何有效的更新UI

  • 如果一棵树参考另外一棵树进行完全比较更新,那么即使是最先进的算法,该算法的复杂程度为 O(n³) ,其中 n 是树中元素的数量

  • 如果在 React 中使用了该算法,那么展示 1000 个元素所需要执行的计算量将在十亿的量级范围

  • 这个开销太过昂贵了,于是,React对这个算法进行了优化,将其优化成了O(n)

    • 同层节点之间相互比较,不会垮节点比较(一旦某个节点不同,那么包括其后代节点都会被替换)
    • 不同类型的节点,产生不同的树结构(当根节点为不同类型的元素时,React 会拆卸原有的树并且建立起新的树)
    • 开发中,可以通过key属性标识哪些子元素在不同的渲染中可能是不变的
  • 在遍历列表时,总是会提示一个警告,让我们加入一个key属性,当子元素拥有 key 时,React 使用 key 来匹配原有树上的子元素以及最新树上的子元素。

    • 在最后位置插入数据 -- 这种情况,有无key意义并不大
    • 在前面插入数据 -- 这种做法,在没有key的情况下,所有的li都需要进行修改
    • 在中间插入元素 -- 新增2014, key为2016元素仅仅进行位移,不需要进行任何的修改
    <ul>
      <li key="2015">Duke</li>
      <li key="2016">Villanova</li>
    </ul><ul>
      <li key="2015">Duke</li>
      <li key="2014">Connecticut</li>
      <li key="2016">Villanova</li>
    </ul>
    

7. 什么是受控组件和非受控组件,如何使用?

  • 受控组件

    • 在 React 中,可变状态通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新
    • 我们将两者结合起来,使React的state成为“唯一数据源”
    • 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作
    • 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”
    this.state = {
      message: ""
    }
    changeInput(event) {
      console.log(event.target.value)
      this.setState({ message: event.target.value })
    }
    ​
    render(){
       <input type="text" value={message} onChange={(event) => this.changeInput(event)} />
    }
    
  • 非受控组件

    • 在受控组件中,表单数据是由 React 组件来管理的
    • 非受控组件中,表单数据将交由 DOM 节点来处理
    this.messageRef.current.value// 在非受控组件中通常使用defaultValue来设置默认值
    render(){
        <input type="text" defaultValue={message} ref={this.messageRef} />
    }
    

8. 什么是高阶组件?高阶组件在React开发中起到什么作用?

  • 高阶函数: (满足一下调教之一) -- filter、map、reduce都是高阶函数

    • 接受一个或多个函数作为输入
    • 输出一个函数
  • 高阶组件: Higher-Order Components,简称为 HOC

    • 高阶组件是参数为组件,返回值为新组件的函数 -- 就是传入一个组件,对这个组件进行一些功能的增强,在返回出来新的组件
    • 注意: 首先 高阶组件 本身不是一个组件,而是一个函数 其次,这个函数的参数是一个组件,返回值也是一个组件
    • HOC 是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
  • 高级组件应用的场景

    • props的增强
    • 利用高阶组件来共享Context
    • 渲染判断鉴权
    • 生命周期劫持
    • ....

9. 什么是Fragment,有什么作用?

  • Fragment 允许将子列表分组,而无需向 DOM 添加额外节点;
  • 它的简写看起来像空标签 <> </>
  • 如果我们需要在Fragment中添加key,那么就不能使用短语法

10. 什么是React的严格模式,在开发中有什么作用?

严格模式

  • StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
  • 与 Fragment 一样,StrictMode 不会渲染任何可见的 UI;
  • 它为其后代元素触发额外的检查和警告;

严格模式作用

  • 识别不安全的生命周期:
  • 使用过时的ref API
  • 检查意外的副作用 这个组件的constructor会被调用两次; 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用; 在生产环境中,是不会被调用两次的;
  • 使用废弃的findDOMNode方法 在之前的React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了,可以自行学习演练一下
  • 检测过时的context API 早期的Context是通过static属性声明Context对象属性,通过getChildContext返回Context对象等方式来使用Context的

11. React中如何实现过渡动画?常见的过渡动画组件有哪些?

  • React社区为我们提供了react-transition-group用来完成过渡动画

常见的过渡动画组件

  • Transition 在前端开发中,一般是结合CSS来完成样式,所以比较常用的是CSSTransition;
  • CSSTransition 在前端开发中,通常使用CSSTransition来完成过渡动画效果
  • SwitchTransition 两个组件显示和隐藏切换时,使用该组件
  • TransitionGroup 将多个动画组件包裹在其中,一般用于列表中元素的动画;

精进

11.8精进:多接触比你层次高的人,远离添麻烦的人,远离心理灰暗的人。
11.9精进
思维方式比工作技能更重要,技能随着时间的流逝可通过很多方式而掌握,而思维方式的形成可以让你更清楚什么时候该做什么样的事情,做事情应该遵循什么样的原则。这是更为重要的,工作很忙的时候一定不要开心自己又进步了,而是停下来思考一下,自己是不是可以更高效一点。
11.10精进
真正让自己成长的,永远是那些让你害怕,逃避,疼痛的事情。
11.11精进
费曼学习法基本步骤
1.确定目标
2.模拟教学
3.反馈
4.简化并重复
与《礼记·学记》中“学,然后知不足,教,然后知困”基本原理非常相似,可见成功的方法最终指同一方向,万物归一。
11.13精进
工作生活中的细节和琐碎,是过不去的坎,得不断总结经验教训,不断优化自己的行动。

参考资料

  • Coderwhy学习资料
  • React官网
  • 解锁前端面试体系核心攻略
  • 鲨鱼哥整理面试资料
  • 其他参考资料 结语

志同道合的小伙伴可以加我,一起交流进步,我们坚持每日精进(互相监督思考学习,如果坚持不下来我可以监督你)。我们一起努力鸭! ——>点击这里

备注

按照时间顺序倒叙排列,完结后按时间顺序正序排列方便查看知识点,工作日更新。