Vue到React迁移笔记(三)指引

589 阅读9分钟

React性能

  • React的的开发模式默认包含了很多有用的警告信息,这使得开发模式下的React性能与实际应该达到的效果大相径庭。所以当你需要关注性能时,请打包为部署版本。
  • 你可以使用Chrome Performance标签分析组件来详细的了解各个组件的挂载、更新、卸载情况。
    • 你需要临时禁用所有的chrome扩展,尤其时React开发者工具来排除他们对性能的影响。
  • 你可以在生命周期方法shouldComponentUpdate中返回false来阻止组件的视图更新
    • 当这个组件的更新缓慢到让人注意从,可以寻找在不需要更新时阻止他的更新。例如当且仅当state中的某个值发生改变才返回true,其他改变可以不更新则返回false。
    • 这个方法默认返回true,他发生在更新之前,true代表允许视图更新

不可变性陷阱

陷阱:props来自state,值改变却没有更新视图。

  • 将父组件的state作为子组件的props,父组件改变了state值,子组件的vDom中保存的是对父组件state的引用,改变后新的值也是父组件的state,导致新旧比较实际上比较的仍然是同一组state。(你在更改时使用了浅拷贝,setState是无效的操作,当然也不会有通知,浅拷贝来的副本在更改时整个调用链都被悄悄更新了)
  • 避免改变你用于props的state值。你可以使用concat来返回一个新的对象来替换地址来引发更新,或者使用扩展运算符。两种方法都要求仅在setState中重设值,而不是通过创建副本的方式改变。
  • 当然你也可以使用深拷贝来做这次操作。例如使用Object.assign返回一个深拷贝对象(可枚举深拷贝)

Profiler——性能测试

Profiler用来测量渲染一个React应用的性能指标,包括多久渲染一次以及其渲染的代价,他可以帮助识别应用中的性能短板。

Profiling增加了额外的开支,他在生产环境中是被禁用的。想做到这一点需要``从 fb.me/react-profi…了解更多关于如何使用这个构建环境的信息。

  • 在JXS中使用<Profiler>标签来包裹你需要检测的部分。
  • 他需要两个prop
    • id:为它取个名字吧~
    • onRender:当前组件树中的组件提交更新时的回调函数
  • 他为你的回调函数传入的参数 image.png

Protals——脱离React树的节点挂载

  • 用于将子节点渲染到任意节点而不是父节点以内的方法,可以做到脱离逻辑的节点挂载。
  • 以下代码的render并没有把子元素渲染进他的调用位置。只是生成了一个domNode,你可以把它挂载在任何有效的dom节点上。
render() {
  return ReactDOM.createPortal(
    this.props.children,
    domNode  );
}  

Protals节点的事件处理

  • 需要注意的是,尽管Protals节点的挂载与逻辑无关,但是他在React树中的位置仍然符合逻辑。所以这仅仅是视图上绑定到了其他地方,但是组件逻辑事件或者数据环境方面仍然正常。
  • 这包含事件冒泡,一个从Protals节点触发的事件会冒泡到React树的父节点。
    • 这里的子元素render使用了ReactDOM.createPortal并且在componentDidMountcomponentWillUnmount中自定义了节点挂载位置,且挂载位置并不属于调用他的父组件结点下。

Diff更新设计逻辑

  • 我们做一些约定,这些约定也是在实际中几乎所有场景都成立的
    • 两个不同类型的元素会产生不同的树
    • 开发者使用keyProp来帮助React确定哪些元素在不同渲染下是稳定的

Diffing算法

  • 对比两棵树时React首先会比较两棵树的根节点tag
    • 当根节点的tag发生了变化,元素类型不同,React会直接拆卸原有的树并构件新的树。例如div变成了a,或者组件A变成了组件B。
      • 拆卸和新键树意味着对应dom节点的销毁和挂载,两个生命周期钩子也很会被执行。
    • 对比同类型的元素,若当前元素的tag比对成功,会比较节点的props属性,找到属性中的不同并改变。
      • 当组件更新,不改变实例来维持state,调用componentWillReceiveProps() 和 componentWillUpdate()方法来更新props值,然后调用render,diff将在新旧结果中进行递归,继续搜索子节点。
  • 继续对比子节点
    • 默认情况下,递归子节点会简单的顺序同步遍历新旧子树序列,对发现的差异生成一个mutation。
    • 当子元素拥有key值,则进行key的对比来确定发生变动的元素。

React,Vue2,Vue3的diff逻辑有所不同,且内容较多,留坑开贴细看。

Render Props

将组件的render中的一部分变成一个由父组件传递的props,组件可以使用自己的state渲染传入的props.render部分。
用来提取有共同的state与行为,但渲染内容不同的组件,当然你的渲染函数可以不叫render。

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>
//在DataProvider组件中render就可以使用props.render来渲染使用自己的状态渲染父组件指定的JSX。
  render() {
    return (
      <div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
        {this.props.render(this.state)}
      </div>
    );
  }

性能陷阱

不建议将Render Props与React.PureComponent共用。 留坑等看完这个API再饭回来写

严格模式

使用React.StrictMode标签包裹你要检查的JSX。
他可以用来突出显示你的程序中的潜在问题,他们仅在开发模式下运行,不会影响部署构建。

  • 识别不安全的生命周期
  • 对过时的或废弃的API使用情况的警告
  • 帮助检测意外的函数副作用

非受控组件

file类型的input

<input type="file">用来让用户选择若干文件上传到服务器,他的值只能由用户设置,而不能通过代码控制,所以他必须是一个非受控组件。

  • 当from中,将onsubmit事件接管并使用event.preventDefault将默认事件阻止。
  • 使用file-input绑定的ref获取其中的文件信息
class FileInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.fileInput = React.createRef();  }
  handleSubmit(event) {
    event.preventDefault();
    alert(
      `Selected file - ${this.fileInput.current.files[0].name}`    );
  }
  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <input type="file" ref={this.fileInput} />
        <button type="submit">Submit</button>
      </form>
    );
  }
}

React.Component API

生命周期

image.png

  • constructor:构造组件实例
  • render:构造react元素或其它形式的组件内容
  • componentDidMount:将react元素挂载到真实dom树后
  • componentDidUpdate:更新react元素后(props或state改变)
  • componentWillUnmount:将组件从DOM中移除前,可以注销监听,清楚timer或取消网络请求
  • componentDidCatch:当渲染过程,生命周期,子函数构造函数中抛出错误时

API

render

  • render:必须实现的方法,当render被调用时,检查props和state的变化并返回以下内容之一
    • React元素:通过JSX创建
    • fragments或数组:使得render返回多个React元素(Vue的template中只能有一个根节点同理)
    • Portals:可以将其渲染到任意dom结点下的元素
    • 文本dom结点
    • null,什么都不渲染
  • render应该是纯函数,且不与浏览器直接交互
  • 如果shouldComponentUpdate返回false,则不会调用render constructor
  • 如果你无需初始化state或不进行方法的绑定则不需要实现constructor
  • 在React.Component子类实现构造函数时,应该首先调用super(props),否则会出现this.props未定义的bug。
  • 避免将props复制给state,除非你要刻意的忽略父元素发来的props更新
    • 关于React官网中一篇关于避免从paops派生state的文档:请保证任何数据的来源都是单一的,且整个组件或项目的数据流动是单向的。做到这一点可以避免很多问题。 componentDidMount
  • 当真实Dom挂在后立即调用,依赖于dom结点的初始化应该实现在此。例如需要请求网络数据此处是实例化请求的好地方。
  • 这里是添加全局订阅的好地方,但是不要忘记在component中取消订阅。
  • 你可以在此调用setstate,他会触发额外的渲染,但是本次渲染会发生在GUI更新之前,这两次render的中间态并不会被用户看到。
  • 请谨慎使用该模式,它会导致性能问题,除非你的渲染依赖于真实dom数据。 componentDidUpdate
  • 参数为prevProps,prevState,snapshot
    • 更新前的老props和state
    • 如果实现了getSnapshotBeforeUpdate生命周期钩子,则第三个函数是这个钩子的返回值
  • 除了首次渲染,每次更新渲染之后会立即调用
  • 比如你可以在此对比props,如果你关注的某个数据发生变化则请求新的对应网络数据,如更换了用户。
  • 你可以在此使用setstate,但必须包裹在条件语句中,否则会产生环路。
  • 如果shouldComponentUpdate返回了false,则不会调用此函数

一些不常用的生命周期方法

shouldComponentUpdate

  • 当props或state更新后,render触发前调用
  • 根据它的返回值来告诉React是否要重新渲染render,默认情况下总会返回true
  • 此方法仅仅用于性能优化的一种方式,不用使用此方法来阻止渲染的更新。
    • 如果你需要阻止React的渲染,考虑使用PureComponent组件。这种组件于普通组件的区别为对props进行浅层比较来跳过必要更新的可能性。
    • 使用PureComponent后,可以使用forceUpdate(强制重新渲染)来确保某些你关注的深层变化时可以被更新渲染。且这种组件会忽视所有子组件树的props更新。
  • 不建议在此进行深层比较或者使用Json.stringify,他们会严重的影响效率
  • 后续版本的React再考虑将此函数的返回值仅作参考而不是强制指令,也就是即使你返回了false也可能会发生重新渲染。 static getDerivedStateFromProps
  • render之前被调用,适用于非常罕见的state值任何时候都取决于props。 getSnapshotBeforeUpdate
  • dom更新之前调用,可以在更新dom之前捕获一些dom信息 static getDerivedStateFromError
  • 在后代组件抛出错误时被调用,将抛出的错误作为参数并返回一个更新state的值,一般用于降级UI。
  • 他在渲染时被调用,不允许出现副作用 componentDidCatch
  • 在后代组件抛出错误后调用,接收两个参数
    • error:抛出的错误
    • ifo:带有componentStack key的对象,包含有关组件引发错误栈的信息。
  • 他在提交阶段被调用,允许副作用。一般用于记录错误

事件

关于React事件参数实例或者封装好的一些常用事件: 合成事件 – React (docschina.org)