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上,本人模仿了事件冒泡和捕捉的过程,并且进行对立的事件散发。