关于react.js:React核心原理与虚拟DOM

3次阅读

共计 7349 个字符,预计需要花费 19 分钟才能阅读完成。

React 根底

JSX

const element = <h1>Hello, world!</h1>;

JSX,既不是字符串也不是 HTML,实质上是一个 JavaScript 的语法扩大,且更靠近于 JavaScript,是通过 React.createElement() 创立的一个对象,称为React 元素

React 不强制应用 JSX,但将标记与逻辑放在一起造成 组件,实现关注点拆散。同时,JSX 可能避免 XSS 注入攻打。

元素渲染

  1. React 元素是不可变对象。一旦被创立,你就无奈更改它的子元素或者属性。
    更新 UI 惟一的形式是创立一个全新的元素,并将其传入 ReactDOM.render()
  2. React 只更新它须要更新的局部。
    React DOM 会将元素和它的子元素与它们之前的状态进行比拟,并只会进行必要的更新来使 DOM 达到预期的状态。

组件 &Props

  • 函数组件:接管惟一带有数据的“props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它实质上就是 JavaScript 函数。
  • class 组件:形如
function Welcome(props) {return <h1>Hello, {props.name}</h1>;
}
class Welcome extends React.Component {render() {return <h1>Hello, {this.props.name}</h1>;
  }
}
  • Props 的只读性: 所有 React 组件都必须像纯函数一样爱护它们的 props 不被更改。
  • state 容许 React 组件随用户操作、网络响应或者其余变动而动静更改输入内容。

组件无论是应用函数申明还是通过 class 申明,都决不能批改本身的 props。
这样的函数被称为“纯函数”,因为该函数不会尝试更改入参,且屡次调用下雷同的入参始终返回雷同的后果。

React 实战视频解说:进入学习

State& 生命周期

setState(updater,[callback])

在 React 中,如果是由 React 引发的事件处理(比方通过 onClick 引发的事件处理),调用 setState 不会同步更新 this.state

为什么要异步?如果 setState 是同步更新 state,而 state 的更新又会触发组件的从新渲染,那么每次 setState 都会渲染组件,这对性能是很大的耗费。

  1. 失常 React 绑定的事件:异步更新
  2. 通过 addEventListener 绑定的事件:同步更新
  3. 通过 setTimeoutt 解决点击事件:同步更新

应用 compoentDidUpdatesetState 的回调函数,来保障在更新利用后触发。
批量更新,是基于一个队列和一个变量锁 isBatchingUpdates 实现。

正确地应用 State 的姿态:

  1. 不要间接批改 State
  2. 调用 setState 不会立刻更新
  3. 所有组件应用的是同一套更新机制,当所有组件 didmount 后,父组件 didmount,而后执行更新
  4. 更新时会把每个组件的更新合并,每个组件只会触发一次更新的生命周期。
  5. 钩子函数和合成事件中:

在 react 的生命周期和合成事件中,react 依然处于他的更新机制中,这时 isBranchUpdate 为 true

依照上述过程,这时无论调用多少次 setState,都会不会执行更新,而是将要更新的 state 存入_pendingStateQueue,将要更新的组件存入dirtyComponent

当上一次更新机制执行结束,以生命周期为例,所有组件,即最顶层组件 didmount 后会将 isBranchUpdate 设置为false。这时将执行之前累积的setState

  1. 异步函数和原生事件中

由执行机制看,setState 自身并不是异步的 ,而是如果在调用 setState 时,如果 react 正处于更新过程,以后更新会被暂存,等上一次更新执行后在执行,这个过程给人一种 异步的假象

在生命周期,依据 JS 的异步机制,会将异步函数先暂存,等所有同步代码执行结束后在执行,这时上一次更新过程曾经执行结束,

isBranchUpdate被设置为false,依据下面的流程,这时再调用 setState 即可立刻执行更新,拿到更新后果。

  1. componentDidMount 调用 setstate

它将会触发一次额定的渲染,然而它将在浏览器刷新屏幕之前产生。这保障了在此状况下即便 render()将会调用两次,用户也不会看到中间状态。

componentDidMount 自身处于一次更新中,咱们又调用了一次 setState,就会在将来再进行一次 render,造成不必要的性能节约,大多数状况能够设置初始值来搞定。

  1. componentWillUpdatecomponentDidUpdate 不能调用 setState, 会造成死循环,导致程序解体。
  2. 举荐:在调用 setState 时应用函数传递 state 值,在回调函数中获取最新更新后的 state。

生命周期:

  1. 挂载
    当组件实例被创立并插入 DOM 中时,其生命周期调用程序如下:
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()

留神:
下述生命周期办法行将过期,在新代码中应该防止应用它们:UNSAFE_componentWillMount()

  1. 更新
    当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用程序如下:
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()

留神:
下述办法行将过期,在新代码中应该防止应用它们:

UNSAFE_componentWillUpdate() UNSAFE_componentWillReceiveProps()

  1. 卸载
    当组件从 DOM 中移除时会调用如下办法:
componentWillUnmount()

事件处理

  1. 在 React 中你不能通过返回 false 来阻止默认行为。必须明确调用 preventDefault

React 本人实现了一套事件机制,本人模仿了事件冒泡和捕捉的过程,采纳了事件代理,批量更新等办法,并且抹平了各个浏览器的兼容性问题。

  1. React 事件与原生事件的执行程序
  2. react 的所有事件都 挂载在 document 中
  • 当实在 dom 触发后冒泡到 document 后才会对 react 事件进行解决
  • 所以原生的事件会先执行
  • 而后执行 react 合成事件
  • 最初执行真正在 document 上挂载的事件
  • react 事件和原生事件能够混用吗?

react 事件和原生事件最好不要混用。

原生事件中如果执行了 stopPropagation 办法,则会导致其余 react 事件生效。因为 所有元素的事件将无奈冒泡到 document 上

this 绑定:你必须审慎看待 JSX 回调函数中的 this,在 JavaScript 中,class 的办法默认不会绑定 this。

办法有三:

  1. 在结构比函数中绑定一下:this.handleClick = this.handleClick.bind(this);
  2. 在类以办法定义事件处理函数时,应用箭头函数:handleClick = () => {console.log('this is:', this);}
  3. 间接在回调函数中应用箭头函数:<button onClick={() => this.handleClick()}>Click me</button>

留神:

[性能优化点]每次渲染 Button 时都会创立不同的回调函数。在大多数状况下,这没什么问题,但如果该 回调函数作为 prop 传入子组件时,这些组件可能会进行额定的从新渲染
咱们通常倡议在结构器中绑定或应用 class fields 语法来防止这类性能问题。

组合 vs 继承

React 有非常弱小的组合模式。咱们 举荐应用组合而非继承 来实现组件间的代码重用。Props 和 组合 为你提供了清晰而平安地定制组件外观和行为的灵便形式。
留神:组件能够承受任意 props,包含根本数据类型,React 元素以及函数。

  1. 应用一个非凡的 {props.children} 来将他们的子组件传递到渲染后果中
  2. 多数状况下,你可能须要在一个组件中预留出几个“洞”。这种状况下,咱们能够不应用 children,而是自行约定:将所需内容传入 props,并应用相应的 prop,相似于槽 slot 的概念。

Context

Context 提供了一种在 组件之间共享此类值的形式,而不用显式地通过组件树的逐层传递 props。

Context 设计目标是为了共享那些对于一个组件树而言是“全局”的数据,例如以后认证的用户、主题或首选语言。

[代码优化点]
Context 次要利用场景在于很多不同层级的组件须要拜访同样一些的数据。请审慎应用 ,因为这会使得组件的复用性变差。
如果你只是想防止层层传递一些属性,组件组合(component composition)有时候是一个比 context 更好的解决方案。

一种无需 context 的解决方案是 将子组件本身传递上来,因此两头组件无需晓得该子组件用到的 props

谬误边界

局部 UI 的 JavaScript 谬误不应该导致整个利用解体,为了解决这个问题,React 16 引入了一个新的概念 —— 谬误边界。

<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

谬误边界是一种 React 组件,这种组件能够捕捉并打印产生在其子组件树任何地位的 JavaScript 谬误,并且,它会渲染出备用 UI,而不是渲染那些解体了的子组件树。

谬误边界在渲染期间、生命周期办法和整个组件树的构造函数中捕捉谬误。

[代码优化点]
谬误边界无奈捕捉以下场景中产生的谬误:

  • 事件处理(理解更多)
  • 异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)
  • 服务端渲染
  • 它本身抛出来的谬误(并非它的子组件)

Refs 转发

Ref 转发是一项将 ref 主动地通过组件传递到其一子组件的技巧。这个技巧对 高阶组件(也被称为 HOC)特地有用
Ref 转发是一个可选个性,其容许某些组件接管 ref,并将其向下传递(换句话说,“转发”它)给子组件。

Fragments

React 中的一个常见模式是一个组件返回多个元素。Fragments 容许你将子列表分组,而无需向 DOM 增加额定节点。

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  );
}

或者应用短语法:<> </>

高阶组件

定义:高阶组件是 参数为组件,返回值为新组件的函数
HOC 不会批改传入的组件,也不会应用继承来复制其行为。相同,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 本身不是 React API 的一部分,它是 一种基于 React 的组合个性而造成的设计模式

const EnhancedComponent = higherOrderComponent(WrappedComponent);

组件是将 props 转换为 UI,而高阶组件是将组件转换为另一个组件。例如:Redux 的 connect

留神:

  1. 不要在 render 办法中应用 HOC。每次调用 render 函数都会创立一个新的 EnhancedComponent,导致子树每次渲染都会进行卸载,和从新挂载的操作!
  2. 务必复制静态方法。你能够应用 hoist-non-react-statics 主动拷贝所有非 React 静态方法
  3. Refs 不会被传递。

与第三方库协同

咱们会 增加一个 ref 到这个根 DOM 元素。在 componentDidMount 中,咱们可能获取它的援用这样咱们就能够把它传递给 jQuery 插件了。

为了避免 React 在挂载之后去触碰这个 DOM,咱们会从 render() 函数返回一个空的 <div />
这个

元素既没有属性也没有子元素,所以 React 没有理由去更新它,使得 jQuery 插件能够自在的治理这部分的 DOM

class SomePlugin extends React.Component {componentDidMount() {this.$el = $(this.el);
    this.$el.somePlugin();}

  componentWillUnmount() {this.$el.somePlugin('destroy');
  }

  render() {return <div ref={el => this.el = el} />;
  }
}

性能优化

  1. 部署时应用生产版本,去除一些正告信息
  2. 虚拟化长列表。” 虚构滚动”技术。这项技术会在无限的工夫内仅渲染无限的内容,并奇迹般地升高从新渲染组件耗费的工夫,以及创立 DOM 节点的数量。react-windowreact-virtualized 是热门的虚构滚动库。
  3. 防止调解。你能够通过 笼罩生命周期办法 shouldComponentUpdate 来进行提速。该办法会在从新渲染前被触发。其默认实现总是返回 true. 如果你晓得在什么状况下你的组件不须要更新,你能够在 shouldComponentUpdate 中返回 false 来跳过整个渲染过程。

继承 React.PureComponent 以代替手写 shouldComponentUpdate()。它用以后与之前 props 和 state 的 浅比拟 覆写了 shouldComponentUpdate() 的实现.

shouldComponentUpdate(nextProps, nextState) {return true;}
  1. 不可变数据的力量。不扭转原来的对象,应用 … 扩大运算符 或 Object.assign 返回新对象。

Diff 算法

  1. 当比照两颗树时,React 首先比拟两棵树的根节点。
  2. 当根节点为 不同类型 的元素时,React 会装配原有的树并且建设起新的树。componentWillUnmount() -> componentWillMount() -> componentDidMount()
  • 当比照两个 雷同类型 的 React 元素时,React 会保留 DOM 节点,仅比对及更新有扭转的属性。而后子节点递归。
  • 子节点递归

在子元素列表开端新增元素时,更新开销比拟小;
如果只是简略的将新增元素插入到表头,那么更新开销会比拟大,不会意识到应该保留前面的,而是会重建每一个子元素。这种状况会带来性能问题。
通过增加 key 来解决。

尽量用雷同的节点类型和稳固可预测的 Key。

Render Prop

render prop 是一个用于告知组件须要渲染什么内容的函数 prop。应用 Props 而非 render。

重要的是要记住,render prop 是因为模式才被称为 render prop,你不肯定要用名为 render 的 prop 来应用这种模式。

将 Render Props 与 React.PureComponent 一起应用时要小心。
如果你在 render 办法里创立函数,那么应用 render prop 会对消应用 React.PureComponent 带来的劣势
因为浅比拟 props 的时候总会失去 false,并且在这种状况下每一个 render 对于 render prop 将会生成一个新的值。

Key 的应用形式

react 依据 key 来决定是销毁从新创立组件还是更新组件,准则是:

  • key 雷同,组件有所变动,react 会只更新组件对应变动的属性。
  • key 不同,组件会销毁之前的组件,将整个组件从新渲染。

应用 index 做 key 存在的问题:

当元素数据源的程序产生扭转时,会从新渲染。而如果应用惟一 ID 作为 key,子组件的值和 key 均未发生变化,只是程序产生扭转,因而 react 只是将他们做了挪动,并未从新渲染。

虚构 DOM

对于是否晋升性能

很多文章说 VitrualDom 能够晋升性能,这一说法实际上是很全面的。

间接操作 DOM 是十分消耗性能的,这一点毋庸置疑。然而 React 应用 VitrualDom 也是无奈防止操作 DOM 的。

如果是 首次渲染,VitrualDom 不具备任何劣势,甚至它要进行更多的计算,耗费更多的内存。

VitrualDom 的劣势在于 React 的 Diff 算法和批处理策略,React 在页面更新之前,提前计算好了如何进行更新和渲染 DOM。
实际上,这个计算过程咱们在间接操作 DOM 时,也是能够本人判断和实现的,然而肯定会消耗十分多的精力和工夫,而且往往咱们本人做的是不如 React 好的。
所以,在这个过程中 React 帮忙咱们 ” 晋升了性能 ”。

所以,我更偏向于说,VitrualDom 帮忙咱们进步了开发效率,在 反复渲染时它帮忙咱们计算如何更高效的更新,而不是它比 DOM 操作更快。

跨浏览器兼容
React 基于 VitrualDom 本人实现了一套本人的事件机制,本人模仿了事件冒泡和捕捉的过程,采纳了事件代理,批量更新等办法,抹平了各个浏览器的事件兼容性问题。

实现原理

  • React 组件的渲染流程
  • 应用 React.createElement 或 JSX 编写 React 组件,实际上所有的 JSX 代码最初都会转换成React.createElement(...),Babel 帮忙咱们实现了这个转换的过程。
  1. createElement 函数对 key 和 ref 等非凡的 props 进行解决,并获取 defaultProps 对默认props 进行赋值,并且对传入的孩子节点进行解决,最终 结构成一个 ReactElement 对象(所谓的虚构 DOM)。
  2. ReactDOM.render 将生成好的虚构 DOM 渲染到指定容器上,其中 采纳了批处理 事务等机制 并且对特定浏览器进行了性能优化,最终转换为实在 DOM
  3. 虚构 DOM 组成
  4. 避免 XSS:借助 Symbol.for(‘react.element’)
  5. 批处理和事务机制:setState
  6. 针对性的性能优化:IE/Edge Fragment
  7. 事件机制:本人实现了一套事件机制,其将所有绑定在虚构 DOM 上的事件映射到真正的 DOM 事件,并 将所有的事件都代理到 document 上 ,本人 模仿了事件冒泡和捕捉的过程,并且进行对立的事件散发。
正文完
 0