react相关问题

你说的曾经没有我的故事 提交于 2020-02-05 11:51:33

什么是JSX?——浏览器是如何识别它的?

JSX是facebook普及的一种标记语言,通过babel/TSC等工具会编译为React.createElementfunction。所以在React每个组件中,虽然没有显式用到React,但都需要import React from 'react'

JSX是如何区分React Component和HTML元素的?

通过元素首字母的大小写,如果首字母大写,则认为是React组件,小写的话则会被认为是HTML元素。可以在online Babel compiler中试一下。

123456789101112
function () {  return <div>Hello world!</div>;}function () {  return React.createElement(    "div",    null,    "Hello world!"  );}

123456789101112
function () {  return <div>Hello world!</div>;}function () {  return React.createElement(    Div,    null,    "Hello world!"  );}

此外,把一个组件赋给this.component并且写<this.component />也会起作用。

react16生命周期

Mounting

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount

子类必须在构造函数第一行执行super(props),否则我们无法在构造函数里拿到this对象,因为子类的实例要通过父类的构造函数完成塑造,得到与父类实例相同的属性和方法。es6的继承是先将是先将父类实例对象的属性和方法,加到this上面(所以必须先调用super方法),然后再用子类的构造函数修改this。ES5 的继承,实质是先创造子类的实例对象this,然后再将父类的方法添加到this上面(Parent.apply(this))
不建议使用UNSAFE_componentWillMount(),因为它是在render之前执行,所以不会触发重新渲染。在该方法内调用setState会和constructor里的初始化state合并执行,应该放到constructor中去初始化。

Updating

  • static getDerivedStateFromProps(nextProps, prevState) //会返回一个对象来更新当前的state对象
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate(prevProps, prevState) 在dom更新前,可以保存之前的滚动位置,便于在componentDidUpdate中去恢复
  • componentDidUpdate(prevProps, prevState, snapshot) 在dom更新后,snapshot的值是getSnapshotBeforeUpdate的返回值

不建议使用UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate(),一次更新可能会调用多次。
UNSAFE_componentWillReceiveProps()经常会带来bug和不一致的问题,例如在该方法中设置input值,当父组件更新的时候,用户的输入就会被置空。

1234567891011121314151617
class EmailInput extends Component {  state = { email: this.props.email };  render() {    return <input onChange={this.handleChange} value={this.state.email} />;  }  handleChange = event => {    this.setState({ email: event.target.value });  };  componentWillReceiveProps(nextProps) {    // This will erase any local state updates!    // Do not do this.    this.setState({ email: nextProps.email });  }}

Unmounting

  • componentWillUnmount()

getDerivedStateFromProps(props, state)每次render之前都会被触发,与componentWillReceiveProps只在父组件rerender时会触发不一样。此外,getDerivedStateFromProps方法不建议经常使用,使用前想一想是否有替代方案。

错误捕获

  • static getDerivedStateFromError(error)
  • componentDidCatch(error, info)
    错误边界(Error Boundaries) 仅可以捕获其子组件的错误。
    使用static getDerivedStateFromError()在抛出错误后渲染回退UI。 使用 componentDidCatch() 来记录错误信息。

异步加载

React V16.6引入的新特性lazy,以及React-loadable库。

getDerivedStateFromProps应用

加分题:数据获取为什么用 componentDidMount 而不是 constructor?

你希望听到的两个原因会是:“在渲染发生之前数据不会存在” —— 虽然不是主要原因,但它向您显示该人员了解组件的处理方式; “在 React Fiber 中使用新的异步渲染……” —— 有人一直在努力学习。

  • r1: SSR模式下,componentWillMount在server端也是会被调用的,内容返回到client端后,componentWillMount会被第二次调用,如果在componentWillMount中处理数据获取则会被调用两次。
  • r2: 在componentWillMount中调用setState不会触发rerender,所以一般不会被用来获取数据。
  • r3: React16之后采用了Fiber架构,类似ComponentWillMount的生命周期钩子都有可能执行多次,所以不在这些生命周期中做有副作用的操作,比如请求数据。
  • r4: constructor用来初始化组件,作用应该保持纯粹,不应该引入数据获取这种有副作用的操作。

react fiber纤程

fiber是纤程颗粒化的概念,一个线程可以包含多个Fiber,主要是对react更新机制的优化。React16之前的版本,更新组件会一直占用主线程,如果组件树过大,则可能会导致浏览器失去响应。在React16中加入的fiber可以将同步任务拆解,每次执行完一小片后,都会把控制权交还给react负责任务调度的模块,如果有优先级更高的任务,就先执行高优先级的任务。

大专栏  react相关问题headerlink" title="拆什么">拆什么

首先,看React的渲染,包括两个阶段:调度阶段(reconciliation)和渲染阶段(commit)。

  • 调度阶段react根据数据更新virtual DOM,再运用diff算法找到需要VDOM change。这一部分的工作是可以拆分的。
  • 渲染阶段根据计算出的所有diff去一次性更新真实的DOM。
    组件比较庞大时,diff运算会比较耗时,不可控,所以需要拆分的是调度阶段。
怎么拆

fiber tree的部分结构如下所示,将简单的数结构,变成了基于单链表的树结构。

12345678910
{    alternate: Fiber|null, //在fiber更新时克隆出的镜像fiber,对fiber的修改会标记在这个fiber上    nextEffect: Fiber | null, // 单链表结构,方便遍历 Fiber Tree 上有副作用的节点    pendingWorkPriority: PriorityLevel, // 标记子树上待更新任务的优先级    stateNode: any, // 管理 instance 自身的特性    return: Fiber|null, // 指向 Fiber Tree 中的父节点    child: Fiber|null, // 指向第一个子节点    sibling: Fiber|null, // 指向兄弟节点}

执行顺序

因为是单链表(A → B → C)的结构,所以在每次执行到某个节点(A → B)被中断后,下次可以从该节点(B → C)接着执行。
requestIdleCallback会让一个低优先级的任务在空闲期被调用,而requestAnimationFrame会让一个高优先级的任务在下一个栈帧被调用,从而保证了主线程按照优先级执行 fiber 单元。

优先级顺序为:文本框输入 > 本次调度结束需完成的任务 > 动画过渡 > 交互反馈 > 数据更新 > 不会显示但以防将来会显示的任务。

因为react fiber机制,一个任务很可能执行到一半就被其他优先级更高的任务所替代,或者因为时间原因而被终止。当再次执行这个任务时,是从头开始执行一遍,就会导致组件的某些 will 生命周期可能被多次调用而影响性能。

REFs:

react事件注册分发

事件注册的时候,react 把所有事件都委托到了document上,减少注册事件的数量,降低内存占用。
事件被触发后,冒泡到document处,找到触发的dom和react component,当前触发的事件会被加入batchedUpdates批处理队列中,在事件对列中,事件分发的核心handleTopLevel保证了子元素在父元素前面(此处分析的是trapBubbledEvent)。
事件执行的时候,首先找到合适的plugin(v16版本有5种)构造对应的合成事件,第一次触发new 创建的事件对象会放到缓存池中,下次直接从对象池中取。最后,就是拿到与事件相关的元素实例和回调函数。

onClickCapture

如果想要注册捕获事件,可以使用onClickCapture。但React的合成事件都是统一注册在document元素上的,且只有冒泡阶段,但合成事件会区分捕获和冒泡两种类型,来保证合成事件的执行顺序。此外,原生事件的执行都会早于合成事件的执行,因为合成事件都要等到事件冒泡到document上,才会执行。
执行顺序是:原生捕获事件 -> 原生事件冒泡 -> 合成事件捕获 -> 合成事件冒泡

react 源码

Component和PureComponent比较

React组件继承了Component或者PureComponent,这两个父类的定义在ReactBaseClasses.js中。

12345678910111213141516171819
function ComponentDummy() {}ComponentDummy.prototype = Component.prototype;/** * Convenience component with default shallow equality check for sCU. */function PureComponent(props, context, updater) {  this.props = props;  this.context = context;  // If a component has string refs, we will assign a different object later.  this.refs = emptyObject;  this.updater = updater || ReactNoopUpdateQueue;}const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());pureComponentPrototype.constructor = PureComponent;// Avoid an extra prototype jump for these methods.Object.assign(pureComponentPrototype, Component.prototype);pureComponentPrototype.isPureReactComponent = true;

从源码上看PureComponent继承了Component,新增了isPureReactComponent变量。
在shouldComponentUpdate函数中,可以看到如果isPureReactComponent为true,会shollow比较新旧props和state是否相等,都相等的话会返回false。而Component始终会返回true。

12345
if (type.prototype && type.prototype.isPureReactComponent) {  return (    !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)  );}

在使用PureComponent时要注意:

  1. 尽量不要在render函数中给子组件绑定新的函数,如下面
    1
    <ChildComponent onclick={() => this.handleClick()}/>

这样每次render都会创建一个新的函数,改变了子组件的props参数,使子组件重新渲染。

  1. 尽量不要在render方法内生成数据
    生成新的数据,例如新的list数组,也会带来不必要的重新渲染。
    1234
    let list = [xxx];...{list.map(item => (<LiItem value={item}/>))}<ChildComponent onclick={() => this.handleClick()}/>

因此相比于 Component ,PureComponent 有性能上的更大提升:

  • 减少了组件无意义的重渲染(当 state 和 props 没有发生变化时),当结合 immutable 数据时其优更为明显;
  • 隔离了父组件与子组件的状态变化;
  1. 若是数组和对象等引用类型,则要引用不同,才会渲染
  2. 如果prop和state每次都会变,那么PureComponent的效率还不如Component,因为你知道的,进行浅比较也是需要时间

key值的作用

  • 提高diff的效率
  • 避免就地复用
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!