likes
comments
collection
share

21天筑基期--React系列

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

React系列

React,用于构建用户界面的 JavaScript 库,只提供了 UI 层面的解决方案 React特性有很多,如:

  • JSX语法 将原始 HTML 模板嵌入到 JS 代码中

  • 单向数据绑定

  • 虚拟DOM

  • 声明式编程

  • Component

  • 高效灵活

  • 声明式的设计,简单使用

  • 组件式开发,提高代码复用率

  • 单向响应的数据流会比双向绑定的更安全,速度更快

着重介绍下声明式编程及Component

React生命周期的各个阶段是什么?

挂载

constructor 可以进行state和props的初始化

static getDerivedStateFromProps

render

componentDidMount 第一次渲染后调用,可以访问DOM,进行异步请求和定时器、消息订阅

更新

当组件的props或state变化会触发更新

static getDerivedStateFromProps

shouldComponentUpdate 返回一个布尔值,默认返回true,可以通过这个生命周期钩子进行性能优化,确认不需要更新组件时调用

render

getSnapShotBeforeUpdate

componentDidUpdate 在组件完成更新后调用

卸载

componentWillUnmount 组件从DOM中被移除的时候调用

错误捕获

static getDerivedStateFromError 在errorBoundary中使用

componentDidCatch

render是class组件中唯一必须实现的方法

虚拟 DOM 的原理是什么?

Real DOMVirtual DOM
1. 更新缓慢。1. 更新更快。
2. 可以直接更新 HTML。2. 无法直接更新 HTML。
3. 如果元素更新,则创建新DOM。3. 如果元素更新,则更新 JSX 。
4. DOM操作代价很高。4. DOM 操作非常简单。
5. 消耗的内存较多。5. 很少的内存消耗。
  1. 是什么

    虚拟 DOM 就是虚拟节点(这句汉化很重要)。React 用 JS 对象来模拟 DOM 节点,然后将其渲染成真实的 DOM 节点。

  1. 怎么做

    第一步是模拟

    用 JSX 语法写出来的 div 其实就是一个虚拟节点:

    <div id="x">
      <span class="red">hi</span>
    </div>
    

    这代码会得到这样一个对象:

    
    {
      tag: 'div',
      props: {
        id: 'x'
      },
      children: [
        {
          tag: 'span',
          props: {
            className: 'red'
          },
          children: [
            'hi'
          ]
        }
      ]
    }
    

    能做到这一点是因为 JSX 语法会被转译为 createElement 函数调用(也叫 h 函数),如下:

    React.createElement("div", { id: "x"}, 
      React.createElement("span", { class: "red" }, "hi")
    )
    

    第二步是将虚拟节点渲染为真实节点

    function render(vdom) {
      // 如果是字符串或者数字,创建一个文本节点
      if (typeof vdom === 'string' || typeof vdom === 'number') {
        return document.createTextNode(vdom)
      }
      const { tag, props, children } = vdom
      // 创建真实DOM
      const element = document.createElement(tag)
      // 设置属性
      setProps(element, props)
      // 遍历子节点,并获取创建真实DOM,插入到当前节点
      children
        .map(render)
        .forEach(element.appendChild.bind(element))
    
      // 虚拟 DOM 中缓存真实 DOM 节点
      vdom.dom = element
      
      // 返回 DOM 节点
      return element
    }
    
    function setProps // 略
    function setProp // 略
    
    

    注意,如果节点发生变化,并不会直接把新虚拟节点渲染到真实节点,而是先经过 diff 算法得到一个 patch 再更新到真实节点上。

  1. 解决了什么问题

    1. DOM 操作性能问题。通过虚拟 DOM 和 diff 算法减少不必要的 DOM 操作,保证性能不太差
    1. DOM 操作不方便问题。以前各种 DOM API 要记,现在只有 setState
  1. 优点

    1. 为 React 带来了跨平台能力,因为虚拟节点除了渲染为真实节点,还可以渲染为其他东西。
    1. 让 DOM 操作的整体性能更好,能(通过 diff)减少不必要的 DOM 操作。
  1. 缺点

    1. 性能要求极高的地方,还是得用真实 DOM 操作(目前没遇到这种需求)
    1. React 为虚拟 DOM 创造了合成事件,跟原生 DOM 事件不太一样,工作中要额外注意

      1. 所有 React 事件都绑定到根元素,自动实现事件委托
      1. 如果混用合成事件和原生 DOM 事件,有可能会出 bug

state 和 props

一、state

一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就是state,一般在 constructor 中初始化

当需要修改里面的值的状态需要通过调用setState来改变,从而达到更新组件内部数据的作用,并且重新调用组件render方法,如下面的例子:

    class Button extends React.Component {
        constructor() {
            super();
            this.state = {
                count: 0,
            };
        }

        updateCount() {
            this.setState((prevState, props) => {
                return { count: prevState.count + 1 }
            });
        }

        render() {
            return (<button
                        onClick={() => this.updateCount()}
                        >
                    Clicked {this.state.count} times
                </button>);
        }
    }

setState还可以接受第二个参数,它是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成

    this.setState({
      name:'JS每日一题'
    },()=>console.log('setState finished'))

二、props

React的核心思想就是组件化思想,页面会被切分成一些独立的、可复用的组件

组件从概念上看就是一个函数,可以接受一个参数作为输入值,这个参数就是props,所以可以把props理解为从外部传入组件内部的数据

react具有单向数据流的特性,所以他的主要作用是从父组件向子组件中传递数据

props除了可以传字符串,数字,还可以传递对象,数组甚至是回调函数,如下:

    class Welcome extends React.Component {
      render() {
        return <h1>Hello {this.props.name}</h1>;
      }
    }
   const element = <Welcome name="Sara" onNameChanged={this.handleName} />;

上述name属性与onNameChanged方法都能在子组件的props变量中访问

在子组件中,props在内部不可变的,如果想要改变它看,只能通过外部组件传入新的props来重新渲染子组件,否则子组件的props和展示形式不会改变

React组件通信

组件传递的方式有很多种,根据传送者和接收者可以分为如下:

  • 父组件向子组件传递
  • 子组件向父组件传递
  • 兄弟组件之间的通信
  • 父组件向后代组件传递
  • 非关系组件传递

父组件向子组件传递

由于React的数据流动为单向的,父组件向子组件传递是最常见的方式

父组件在调用子组件的时候,只需要在子组件标签内传递参数,子组件通过props属性就能接收父组件传递过来的参数

    function EmailInput(props) {
      return (
        <label>
          Email: <input value={props.email} />
        </label>
      );
    }

    const element = <EmailInput email="123124132@163.com" />;

子组件向父组件传递

子组件向父组件通信的基本思路是,父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值

父组件对应代码如下:

    class Parents extends Component {
      constructor() {
        super();
        this.state = {
          price: 0
        };
      }

      getItemPrice(e) {
        this.setState({
          price: e
        });
      }

      render() {
        return (
          <div>
            <div>price: {this.state.price}</div>
            {/* 向子组件中传入一个函数  */}
            <Child getPrice={this.getItemPrice.bind(this)} />
          </div>
        );
      }
    }

子组件对应代码如下:

    class Child extends Component {
      clickGoods(e) {
        // 在此函数中传入值
        this.props.getPrice(e);
      }

      render() {
        return (
          <div>
            <button onClick={this.clickGoods.bind(this, 100)}>goods1</button>
            <button onClick={this.clickGoods.bind(this, 1000)}>goods2</button>
          </div>
        );
      }
    }

兄弟组件之间的通信

如果是兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递

    class Parent extends React.Component {
      constructor(props) {
        super(props)
        this.state = {count: 0}
      }
      setCount = () => {
        this.setState({count: this.state.count + 1})
      }
      render() {
        return (
          <div>
            <SiblingA
              count={this.state.count}
            />
            <SiblingB
              onClick={this.setCount}
            />
          </div>
        );
      }
    }

父组件向后代组件传递

父组件向后代组件传递数据是一件最普通的事情,就像全局数据一样

使用context提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据

通过使用React.createContext创建一个context

     const PriceContext = React.createContext('price')

context创建成功后,其下存在Provider组件用于创建数据源,Consumer组件用于接收数据,使用实例如下:

Provider组件通过value属性用于给后代组件传递数据:

    <PriceContext.Provider value={100}>
    </PriceContext.Provider>

如果想要获取Provider传递的数据,可以通过Consumer组件或者或者使用contextType属性接收,对应分别如下:

    class MyClass extends React.Component {
      static contextType = PriceContext;
      render() {
        let price = this.context;
        /* 基于这个值进行渲染工作 */
      }
    }

Consumer组件:

    <PriceContext.Consumer>
        { /*这里是一个函数*/ }
        {
            price => <div>price:{price}</div>
        }
    </PriceContext.Consumer>

非关系组件传递

如果组件之间关系类型比较复杂的情况,建议将数据进行一个全局资源管理,从而实现通信,例如redux。关于redux的使用后续再详细介绍

你如何理解 Redux

  1. 文档第一句话背下来:Redux 是一个状态管理库/状态容器。
  1. 把 Redux 的核心概念说一下:

    1. State
    1. Action = type + payload 荷载
    1. Reducer
    1. Dispatch 派发
    1. Middleware
  1. 把 ReactRedux 的核心概念说一下:

    1. connect()(Component)
    1. mapStateToProps
    1. mapDispatchToProps
  1. 说两个常见的中间件 redux-thunk redux-promise

高阶组件的理解

高阶函数(Higher-order function),至少满足下列一个条件的函数

  • 接受一个或多个函数作为输入
  • 输出一个函数

参考阅读:「react进阶」一文吃透React高阶组件(HOC) - 掘金 (juejin.cn)

React中hooks的优缺点是什么?

React Hooks实在是泰裤辣! - 掘金 (juejin.cn)

Hook的优点

  1. 让函数组件拥有自己的状态和生命周期。
  2. 使用函数组件加Hooks代码更加简洁。
  3. 不需要老是去纠结this指向的问题。
  4. 通过自定义hooks实现逻辑复用。

Hook的点:

class组件的三个生命周期函数合并在一个生命周期函数内。

useCallback 和 useMemo 的使用场景

useCallback 和 useMemo 可以用来缓存函数和变量,提高性能,减少资源浪费。但并不是所有的函数和变量都需要用这两者来实现,他也有对应的使用场景。

我们知道 useCallback 可以缓存函数体,在依赖项没有变化时,前后两次渲染时,使用的函数体是一样的。它的使用场景是:

  • 函数作为其他 hook 的依赖项时(如在 useEffect()中);
  • 函数作为 React.memo()(或 shouldComponentUpdate )中的组件的 props;

主要是为了避免重新生成的函数,会导致其他 hook 或组件的不必要刷新。

useMemo 用来缓存函数执行的结果。如每次渲染时都要执行一段很复杂的运算,或者一个变量需要依赖另一个变量的运算结果,就都可以使用 useMemo()。

React Hooks 如何模拟组件生命周期?

21天筑基期--React系列

  1. 模拟 componentDidMount
  // componentDidUpdate
  useEffect(()=>{
    if(first.current === true ){
      return
    }
    console.log('did update')
  })

  1. 模拟 componentDidUpdate和componentWillUnmount
  // componentDidMount
  useEffect(()=>{
    console.log('did mount')
    first.current = false
    return ()=>{
      console.log('did unmount')
    }
  }, [])

Key的作用是什么

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

当组件刷新时,React 内部会根据 key 和元素的 type,来对比元素是否发生了变化。若选做 key 的数据有问题,可能会在更新的过程中产生异常 21天筑基期--React系列

Diff算法

React中的diff算法 - 知乎 (zhihu.com)