React遗漏的知识点

319 阅读9分钟

*受控组件 V.S 非受控组件

 <FInput value={x} onChange={fn}/> 受控组件
 <FInput defaultValue={x} ref={input}/> 非受控组件

*React 有哪些生命周期函数?分别有什么用?(Ajax 请求放在哪个阶段?)

链接

image-20210624162011783

初始化

constructor

挂载

* 2.render 
* 3.componentDidMount
    发生在 render 函数之后,已经挂载 Dom

更新

* props
    * 2. shouldComponentUpdate(nextProps,nextState)
        这个生命周期需要返回一个 Boolean 类型的值,判断是否需要更新渲染组件,优化 react 应用的主要手段之一,当返回 false 就不会再向下执行生命周期了,在这个阶段不可以 setState(),会导致循环调用。
    * 4. render
        执行 render 函数。
    * 5. componentDidUpdate(prevProps, prevState) 
        在此时已经完成渲染,Dom 已经发生变化,State 已经发生更新,prevProps、prevState 均为上一个状态的值。
* state(具体同上)
    * 1. shouldComponentUpdate
    * 3. render
    * 4. componentDidUpdate

卸载阶段

* componentWillUnmount
componentWillUnmount 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount  中创建的订阅等。componentWillUnmount 中不应调用 setState,因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。

在componentDidMount这个里面发送ajax请求

*React 如何实现组件间通信?

父子组件通信

父组件向子组件通信,可以通过 props 方式传递数据;也可以通过 ref 方式传递数据;

子组件向父组件通信,通过回调函数方式传递数据;

// 父组件
import React, { useState } from "react";
import Child from "./Child";

function Father() {
  const [state, setState] = useState("state");

  const changeName = () => {
    setState("ade");
  };
  return (
    <div>
      <Child state={state} changeName={changeName} />
    </div>
  );
}
export default Father;
// 子组件
import React, { useState } from "react";

function Child(props) {
  const { state } = props;
  const onButtonClick = () => {
    props.changeName();
  };
  return (
    <>
      <div>{state}</div>
      <button onClick={onButtonClick}>click</button>
    </>
  );
}

export default Child;

非父子组件通信

父组件向后代所有组件传递数据,如果组件层级过多,通过 props 的方式传递数据很繁琐,可以通过 Context.Provider 的方式;

// 爷组件
import React, { useState, createContext } from "react";
import Child from "./Child";

export const C = createContext(null);

function Father() {
  const [theme, setTheme] = useState("blue");
  return (
    <div>
      <C.Provider value={{ theme, setTheme }}>
        <p>{theme}</p>
        <Child setTheme={setTheme}></Child>
      </C.Provider>
    </div>
  );
}

export default Father;
// 孙组件import React, { useContext } from "react";import { C } from "./Father";function GrandSon() {  const { theme, setTheme } = useContext(C);  return (    <div>      <h1>孙子组件</h1>      <p>{theme}</p>      <button onClick={() => setTheme("blue")}>蓝色</button>    </div>  );}export default GrandSon;

*shouldComponentUpdate 有什么用?

用于拦截组件渲染。返回true允许更新,false不更新

setState到底是异步还是同步?

setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的。

setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。

setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。

*虚拟 DOM 是什么?React diff 的原理是什么?

在 React 中,render 执行的结果得到的并不是真正的 DOM 节点,结果仅仅是轻量级的 JavaScript 对象,我们称之为 virtual DOM。

如果没有 Virtual DOM,简单来说就是直接重置 innerHTML。

比较 innerHTML 和 Virtual DOM 的重绘过程如下:

innerHTML: render html string + 重新创建所有 DOM 元素

Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新

虚拟DOM的原理

  • 虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象
  • 状态变更时,记录新树和旧树的差异
  • 最后把差异更新到真正的dom中

React diff与传统diff区别

传统diff通过循环递归对节点进行依次对比,效率低下,算法复杂度达到 O(n^3),其中 n 是树中节点的总数。O(n^3) 到底有多可怕,这意味着如果要展示1000个节点,就要依次执行上十亿次的比较。

React 分别对 tree diff、component diff 以及 element diff 进行算法优化。

tree diff

利用Virtual DOM树进行 分层比较、层级控制。如果出现跨层次的移动操作,会直接删除之前的,在需要的地方重新创建。

image-20210719155300824

component diff

  • 对于同一类组件,一方面:按照原策略继续比较 virtual DOM tree。另一方面:可以人为的用shouldComponentUpdate()判断Virtual DOM是否发生了变化,若没有变化就不需要在进行diff,这样可以节省大量时间,若变化了,就对相关节点进行update。
  • 对于非同一类的组件,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。(注意是直接删除,然后创建)

element diff

React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。没有key 的直接使用diff差异化对比,删除后创建新的。

React 通过设置唯一 key的策略,对 element diff 进行算法优化,如果有key,节点位置不同,就进行位置移动。

*什么是高阶组件?

高阶组件本身不是一个组件, 而是一个函数 ,这个函数的参数是一个组件, 返回值是一个组件

ref的转发(获取函数式组件DOM)

// 父组件import React, { useRef, forwardRef, useEffect } from "react";import GrandSon from "./GrandSon";const GrandSon2 = forwardRef(GrandSon);function Child(props) {  const { setTheme } = props;  const sonRef = useRef(null);  useEffect(() => {    console.log(sonRef.current);  }, []);  return (    <>      <h1>儿子组件</h1>      <button onClick={() => setTheme("red")}>红色</button>      <GrandSon2 ref={sonRef} />    </>  );}export default Child;
// 子组件import React, { useContext } from "react";import { C } from "./Father";function GrandSon(props, ref) {  const { theme, setTheme } = useContext(C);  return (    <div>      <h1>孙子组件</h1>      <p>{theme}</p>      <button ref={ref} onClick={() => setTheme("blue")}>        蓝色      </button>    </div>  );}export default GrandSon;

常用的高阶组件有属性代理

function HOC(WrapComponent){    return class MyName extends React.Component{       state={           name:'ade'       }       render(){           return <WrapComponent  { ...this.props } { ...this.state }  />       }    }}

HOC的作用

props的增强 :不修改原有代码的情况下,添加新的props

渲染劫持:在开发中,我们可能遇到这样的场景:某些页面是必须用户登录成功才能进行进入;如果用户没有登录成功,那么直接跳转到登录页面。

修改渲染树

* Redux 是什么?

Redux 是一个应用状态管理js库

React-Redux 是连接 React 应用和 Redux 状态管理的桥梁。React-redux 主要专注两件事,一是如何向 React 应用中注入 redux 中的 Store ,二是如何根据 Store 的改变,把消息派发给应用中需要状态的每一个组件。

核心概念

Store:保存数据的地方,你可以把它看成一个容器,整个应用只能有一个Store。

State:Store对象包含所有数据,如果想得到某个时点的数据,就要对Store生成快照,这种时点的数据集合,就叫做State。

Action:State的变化,会导致View的变化。但是,用户接触不到State,只能接触到View。所以,State的变化必须是View导致的。Action就是View发出的通知,表示State应该要发生变化了。

Action Creator:View要发送多少种消息,就会有多少种Action。如果都手写,会很麻烦,所以我们定义一个函数来生成Action,这个函数就叫Action Creator。

Reducer:Store收到Action以后,必须给出一个新的State,这样View才会发生变化。这种State的计算过程就叫做Reducer。Reducer是一个函数,它接受Action和当前State作为参数,返回一个新的State。

dispatch:是View发出Action的唯一方法。

核心api

createStore

createStore redux中通过 createStore 可以创建一个 Store ,使用者可以将这个 Store 保存传递给 React 应用,具体怎么传递那就是 React-Redux 做的事了。首先看一下 createStore 的使用:

const Store = createStore(rootReducer,initialState,middleware)
  • 参数一 reducers : redux 的 reducer ,如果有多个那么可以调用 combineReducers 合并。

  • 参数二 initialState :初始化的 state 。

  • 参数三 middleware :如果有中间件,那么存放 redux 中间件。

combineReducers

/* 将 number 和 PersonalInfo 两个reducer合并   */const rootReducer = combineReducers({ number:numberReducer,info:InfoReducer })
  • 正常状态可以会有多个 reducer ,combineReducers 可以合并多个reducer。

applyMiddleware

const middleware = applyMiddleware(logMiddleware)
  • applyMiddleware 用于注册中间价,支持多个参数,每一个参数都是一个中间件。每次触发 action ,中间件依次执行。

connect 的原理是什么?

connect 提供的功能,做数据获取,数据通信,状态派发等操作。

function connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)

mapStateToProps:组件依赖 redux 的 state,映射到业务组件的 props 中,state 改变触发,业务组件 props 改变,触发业务组件更新视图。

mapDispatchToProps:将 redux 中的 dispatch 方法,映射到业务组件的 props 中。

mergeProps:stateProps , state 映射到 props 中的内容 ; dispatchProps, dispatch 映射到 props 中的内容; ownProps 组件本身的 props。

//  ComponentAimport React, {useState} from 'react'import {connect} from 'react-redux'function ComponentA({toCompB, compBsay}) { /* 组件A */  const [CompAsay, setCompAsay] = useState('')  return (    <div className="box">      <p>我是组件A</p>      <div> B组件说:{compBsay} </div>      对B组件说:<input placeholder=" 对B组件说" onChange={(e) => setCompAsay(e.target.value)}/>      <button onClick={() => toCompB(CompAsay)}>确定</button>    </div>  )}/* 映射state中CompBsay  */const CompAMapStateToProps = state => ({compBsay: state.info.compBsay})/* 映射toCompB方法到props中 */const CompAmapDispatchToProps = dispatch => ({toCompB: (mes) => dispatch({type: 'SET', payload: {compAsay: mes}})})/* connect包装组件A */const ConnFather = connect(CompAMapStateToProps, CompAmapDispatchToProps)(ComponentA)export default ConnFather
// ComponentBimport React, {useState} from 'react'import {connect} from 'react-redux'function ComponentB(props) { /* B组件 */  const [compBsay, setCompBsay] = useState('')  const handleToA = () => {    props.dispatch({type: 'SET', payload: {compBsay: compBsay}})  }  return (    <div className="box">      <p>组件B</p>      <div> A组件对说:{props.compAsay} </div>      对A组件说:<input placeholder="对A组件说" onChange={(e) => setCompBsay(e.target.value)}/>      <button onClick={handleToA}>确定</button>    </div>  )}/* 映射state中 CompAsay  */const CompBMapStateToProps = state => ({compAsay: state.info.compAsay})const ConnSon = connect(CompBMapStateToProps)(ComponentB)export default ConnSon

你是如何理解fiber的?

Fiber是对React核心算法的重构

Fiber 把一个渲染任务分解为多个渲染任务,而不是一次性完成,把每一个分割得很细的任务视作一个"执行单元",React 就会检查现在还剩多少时间,如果没有时间就将控制权让出去,故任务会被分散到多个帧里面,中间可以返回至主进程控制执行其他任务,最终实现更流畅的用户体验。

推荐阅读:完全理解React Fiber | 黯羽轻扬 (ayqy.net)


参考文章

React 源码剖析系列 - 不可思议的 react diff

React 进阶实践指南 - 我不是外星人 - 掘金小册 (juejin.cn)

完全理解React Fiber | 黯羽轻扬 (ayqy.net)