函数式组件 && React Hook

React Component

binlive前端开发,web开发,node,vue,react,webpack
Class组件是咱们如今最多见的组件用法,它拥有组件内的状态和生命周期等一些特性。Class组件是最多见的React组件写法,它须要声明一个class并去继承 react 的Component 类。类组件使用范围普遍,它拥有内部状态、生命周期等一系列方法。
binlive前端开发,web开发,node,vue,react,webpack
函数式组件写法更加简洁,它接收父级传入的 props 对象并返回一个 React 元素,它本质上就是基础的 JavaScript函数

Class Component

React createClass

Class组件的写法建立方法有两种,下图是兼容es5写法的React.createClass定义的组件,这也是react一开始的建立组件的方法。 前端

binlive前端开发,web开发,node,vue,react,webpack

写法说明:
  1. propTypes用来处理校验定义属性的类型校验,PropTypes是从React中获取的,在React16.0.0版本之后废弃了这种写法,PropTypes被单独提取到prop-types包中,因此先比较新的版本中,咱们要进行类型校验,须要手动引入 prop-types 这个模块
  2. getInitialState用来初始化state,getDefaultProps用来定义props的默认属性值,这两个函数的用法有点相似于如今vue中的data和props方法
  3. createClass 组件须要定义一个render函数,它会返回 react node用来渲染视图
ES6的class react

这是一个如今常见的ES6的class react组件写法 vue

binlive前端开发,web开发,node,vue,react,webpack

写法说明:
  1. React 在 v0.13.0 引入了 Component API, 配合一些代码转换工具咱们可使用最新的es语法
  2. 组件一样须要定义一个render函数用来渲染视图,因为es6支持了方法简写,render以及其余定义的函数已经不须要声明function 关键字
  3. 在构造函数中初始化了 state,这里能看到有一个 super 调用,ES6 规定,子类的构造函数必须执行一次super方法,若是子类没有定义constructor方法,这个构造函数会被默认添加并调用super。 若是咱们声明了constructor函数,就必须在构造函数里调用super,只有在正常调用super(props)方法后才能够访问this对象,在constructor中若是要访问this.props须要在super中传入props,固然也能够直接经过props访问。可是不管有没有定义constructor,super是否传入props参数,在react的其余生命周期中this.props都是可使用的,这是React自动附带的。
  4. 继承Component API 建立的组件遵循es6 class的规则,react不会帮咱们自动绑定this,为了能正确访问this,咱们须要在构造函数里绑定this的指向。可是若是方法不少的话,就会周期构造函数里写了不少冗余的绑定函数代码
函数this优雅的解决方案

binlive前端开发,web开发,node,vue,react,webpack
这里要说到 类字段语法这个提案 公有类字段的语法定义是 字段名 = 引用值。 在 constructor 方法被执行前, 实例上已经被赋值了该字段内容。

binlive前端开发,web开发,node,vue,react,webpack
因此如今比较流行的写法是这样的 根据公有类字段语法 咱们能够直接经过 字段名 = state 对象内容来声明state对象。 因为箭头函数没有本身 this,在这里它会指向当前类的实例。借用公有类字段的提案,咱们能够直接声明一个 箭头函数来修正方法的this指向。 这样咱们就无需在构造函数里去建立state。也不须要在构造函数里写一大堆function绑定this。若是没有其余的操做,咱们就彻底能够省略构造函数。

React.PureComponent

binlive前端开发,web开发,node,vue,react,webpack

PureComponent 内部是继承React.Component来的,在React.Component组件中咱们可使用shouldComponentUpdate来判断是否重发从新渲染,而 React.PureComponent 内部使用 shallowEqual 进行浅比较。 浅比较会比较更新先后的state和props的长度是否一致,判断是否新增或者减小了props或state的数据个数。 而后它还会调用内部的objectis方法浅层对比先后的state和prop,objectis相似于es6的 Object.is方法, Object.is 相似于 === 全等运算符,只是在比较 +0 跟 -0 时表现的不太同样, === 返回的是true 而他是false。node

binlive前端开发,web开发,node,vue,react,webpack
因为浅比较的缘由,当props中的参数为引用类型时,修改对象数据中的value时, PureComponent 组件不会触发从新渲染,由于他们是引用的同一个内存地址。

Functional Component

binlive前端开发,web开发,node,vue,react,webpack
函数式组件很是的简洁纯粹,一个函数既是一个组件。

  • 函数组件没有生命周期
  • 也没有它本身的内部状态
  • 由于只是一个函数,在调用时没有内部的 this

上图是一个很简单的函数组件例子,虽说函数组件本质上就是 一个 JavaScript 函数, 在例子里看起来没有任何使用react的api或者方法,可是咱们仍然引入了react,这是由于在组件内部返回的react 元素 使用了 jsx,他实质上是React.createElement的语法糖,配置好babel就能够为咱们编译jsx,简化了咱们写createElement的过程。react

Stateless Component

binlive前端开发,web开发,node,vue,react,webpack
函数式组件在以往咱们也称其为无状态组件(Stateless Component)。函数式组件中并不须要实例化并且也没有生命周期与本身内部的状态管理,它只简单的接受props而后进行渲染。在处理一些简单的没有UI展现性内容时使用无状态组件能更好的进行性能优化。

Class 与 Functional 差别

binlive前端开发,web开发,node,vue,react,webpack

函数组件不须要声明类,能够避免大量的譬如extends或者constructor这样的代码 也不须要处理 this 指向的问题。 更加纯粹的是一个函数就是一个组件,React 官方说的 React 组件一直更像是函数,咱们写函数式组件彷佛也是更加贴近react的原则。 引用透明性是函数式编程的一个概念,我我的以为函数组件遵循了纯函数的概念。webpack

纯函数

binlive前端开发,web开发,node,vue,react,webpack

引用透明性是函数式编程里的概念。 这是一个 redux 里的 reducer 函数。在 redux 里 reducer 须要被定义为一个纯函数,它符合纯函数的几个定义:git

  1. 相同的输入总会有相同的输出
  2. 不会修改函数的输入值 reducer不可以修改state,只能返回一个新的state
  3. 不依赖外部环境状态
  4. 无任何反作用

React Hook

不少时候一开始咱们写的代码或者组件都是比较简单的,咱们可能会选 函数组件来完成一个 简单的功能模块。可是越到后面可能功能就变的越发的复杂了,函数组件内可能须要一些本身的状态或者生命周期了。 这时候想要实现这些功能可能就须要把它借助 高阶组件 或者 render props 帮它包装一层class 的父组件,这样它就间接的拥有了状态跟生命周期。可是这也都只是在函数组件外借助了 class 组件的能力。es6

因为函数组件在每次渲染时候,组件内部都会从上到下从新执行一遍,它也没有办法有本身内部的状态,也没法产生反作用,为了加强函数组件,hook就出现了。github

Vue Hook

binlive前端开发,web开发,node,vue,react,webpack

Vue 在放出可能到来 3.0 更新的内容,上图就是此次更新变更比较大的 funtion based api.web

对比右侧2.0的写法, template 就是 以前的模板语法。比较特殊的就是 setup 函数。 这里的value是一个包装对象,它就是组件内部的状态,这个看起来跟react 的 usestate hook 仍是很是像的。 onMounted 对应的就是之前的生命周期函数 mounted,方法最后的返回对象有点像以前的data方法里的内容被绑定到了template模板语法上,setup 看起来就是一个简单的函数。typescript

binlive前端开发,web开发,node,vue,react,webpack
vue做者提到了 function based api 借鉴了 react hook 的思想。 因为 typescript对函数的类型推导能力比较好,以前vue组件比较流行的那种对象的写法如今看起来也变成了setup这样一个函数了。 至于更灵活的逻辑复用能力,这个在后面会看到是如何进行逻辑复用的。

React Hook

binlive前端开发,web开发,node,vue,react,webpack
Hook 是 React 16.8 的新增特性。它是在函数组件的基础上引入了一些新API,可让你在不编写 class 的状况下使用 state 以及其余的 React 特性。

咱们看到这里使用了一个 useState 的API,它能够在调用时传入参数,而且返回一个数组。数组中有两项内容,第一个是状态(state)、第二个是设置状态(setState),使用es6 数组解构的特性,咱们能够根据咱们的须要去语义化命名,useState中的init参数只会在方法在第一次初始化之后,即便函数组件被更新,它的state也不会被重置,因此它能够一直保留着当前的状态。 这也就让函数组件拥有了内部状态,以及生命周期。

一些hook用法以及注意点

hook跳过state更新

binlive前端开发,web开发,node,vue,react,webpack

在react 的 class 组件中,咱们会定义一个数组类型的 state,在对修改了引用中data里的一些值后,调用this.setState 方法会触发从新渲染,可是在一样的操做在hook里并不会触发更新。

binlive前端开发,web开发,node,vue,react,webpack
调用咱们设置 hook 的 setData 更新函数,数并传入当修改过引用类型的data 去触发更新时,React将跳过子组件的渲染及 effect 的执行。(React 使用 Object.is 比较先后 state)因为引用地址没变化 因此不会发生从新渲染。 咱们可使用图中下面的两种方式浅拷贝对象 而后再进行操做,便可解决这一问题。

useEffect介绍

binlive前端开发,web开发,node,vue,react,webpack
useEffect 正如它的名字同样,他能够在函数组件中使用一些反作用,咱们能够用来模拟一些生命周期操做。 useEffect 拥有两个参数,第一个参数是一个回调函数,它会在完成一些状态更新以及组件渲染后被触发,第二个参数是一个数组是一个可选的参数.

useEffect的规则以下:

  1. 当第二个不存在时,在第一次初始化和每次从新渲染后都会触发回调。
  2. 当数组存在并有值时,若是数组中的任何值发生更改,则每次渲染后都会触发回调。
  3. 当它是一个空数组时,回调只会被触发一次,相似于 componentDidMount。
  4. 每一个 useEffect 均可以返回一个清除函数。

Hooks Rules

binlive前端开发,web开发,node,vue,react,webpack
React 官方文档说明,不要在循环,条件或嵌套函数中调用 Hook,只在最顶层使用 Hook。 只在最顶层使用Hook并非说要将 hooks 代码写在组件的最上面,而是hooks的代码不能套在一些判断或者循环条件中。这些规范要求最终的目的都是要保证当组件建立跟后续的更新时,每次的 hoos 顺序都是一致的。

为何Hooks 须要遵循规则

binlive前端开发,web开发,node,vue,react,webpack
这是react源码中对于Hook的类型声明。

  • memoizedState: 存储的上一次所有update队列执行完成以后的状态值
  • baseState: 初始化 initialState 值, 以及每次 调用setter 触发 dispatch 以后 更新的 newState
  • baseUpdate:当前须要更新的 Update
  • Queue: 当前hook的更新队列,它会存储屡次更新行为,咱们每次 setter 都会 dispatch都会被推到这个队列中

binlive前端开发,web开发,node,vue,react,webpack
咱们依次调用三个 useState hook

React内部会建立对应的 hook 节点,react 第一次初始化组件时内部会将这些节点结构经过 next 依次链接起来,造成一个单向链表结构,每一次调用setter 就 dispach 一个对应的action方法,将action存储在queue中。

在后续触发渲染时,react 会调用 queue 队列中的内容,queue 也是一个链表结构,它是收集咱们调用setter方法,queue 会依次执行内部的action,从而获取到最新的状态值,状态值会被更新到memorizedState上,而后将状态反应到对应的hook字段中造成绑定。因此当咱们打乱或者增长减小hooks时,会形成hook内部顺序错误,从而致使没法正常工做。

binlive前端开发,web开发,node,vue,react,webpack

上图是组合成的一个hooks单向链表结构,内部依次经过next指向下一个hook。

尝试经过修改hook数量,在第一次初始化时候咱们调用了三个usestate hook,react也会在它内部依次收集了三个hook。 第一次渲染时,页面能够正常的渲染出内容,当咱们点击了一个 setter 方法,从新触发了渲染时,会因为 hook 数量比以前的少而致使页面错误,没法正常渲染。因此保证每次都是相同的调用顺序才能正确的使用hook。

binlive前端开发,web开发,node,vue,react,webpack
Hooks API是专门为函数组件设计的,因此咱们只能在函数式组件内部或者自定义的hook中使用hooks,包括usestate、useeffect、userref 等以及自定义的hook api。并且自定义hook,也是没法在class组件内部使用的。 固然正常的函数组件,即便在内部使用了hook,咱们能够也class组件内部复用该函数组件,这个是不会影响的。

Hook 生命周期

binlive前端开发,web开发,node,vue,react,webpack
Constructor: 咱们之前在constructor里更多的是用来初始化state,在使用hook建立state时能够传入 initialState 初始化便可, 若是你有一些复杂的计算逻辑,初始化的参数还能够是一个函数,只要返回计算后的值便可

componentDidMount: 当第二个参数为空数组时,回调只会被触发一次,相似于componentDidMount。

binlive前端开发,web开发,node,vue,react,webpack
componentDidUpdate: 当 useEffect 不传入第二个参数时,在第一次初始化和每次从新渲染后都会触发回调。这与 componentDidUpdate 有些不一样,componentDidUpdate 生命周期在初始化挂载的时候不会被调用,咱们可使用 useRef 钩子来存储一个值来判断函数是否为第一次调用

useRef返回一个可变的ref对象,它不只能够绑定dom的引用,也能够用来存储一些值。其.current属性被初始化为传递的参数(initialValue)。返回的对象将持续整个组件的生命周期。变动 .current 属性不会引起组件从新渲染。

componentWillUnmount 前面提到每一个 useEffect 均可以返回一个清除函数。配合这一特性,咱们能够任然将第二个参数设置为一个空数组,这样清除函数会在组件卸载前被调用,咱们能够在这里面清除一些事件监听器等。更方便的是咱们能够将 didMount 和 unMount 写在同一个 useEffect 钩子中。

binlive前端开发,web开发,node,vue,react,webpack
componentWillReceiveProp: 咱们想要模拟以前的 componentWillReceiveProps 生命函数,能够将第二个参数设为须要观察的props参数。为了比对先后的 props 变化,能够用 useRef 来存储旧的props来达到目的。

setState的第二个参数 咱们知道在使用setState方法时,能够传入第二个参数,这个参数是个回调函数,它在设置完 state 之后 会被调用。 咱们在hook里也能够经过用useEffect 钩子传入要监听的 state,当该state更新之后,回调函数会被调用。

逻辑复用

binlive前端开发,web开发,node,vue,react,webpack
在vue和react官方都提到了hook能够更好的逻辑复用,他们的动机都是由于组件间一些重复的逻辑代码没法复用,咱们看下如何使用react hooks完成逻辑代码的复用。

自定义Hook

binlive前端开发,web开发,node,vue,react,webpack
这是一个自定义 hook,也就是咱们本身建立一个hook。

React 规定 自定义hook 须要用 use 开头来定义,自定义hook虽然不是一个组件,它不须要返回 React Node 元素节点,若是返回了元素,它就又变成的一个函数组件,可是虽然这样,它一样的能够在方法内部使用 状态 usestate 和生命周期 useeffect 等其余的 hooks。他拥有本身的内部状态和生命周期。

比较特殊的是 自定义hook 返回的内容能够任何类型,你能够单纯的返回一个值,也能够返回一个对象或者数组甚至是方法,返回的内容取决于在调用你建立的自定义hook时所须要的一些属性。

用法说明:

咱们在这里定义了一个名为 useOnline 的自定义hook, 咱们用它来判断当前的网络链接状态。首先hook的方法名是以use开头的,后面的名字能够根据你的意愿来定义,大部分时候hook都会遵循驼峰命名法,只要是以use开头,后面的定义是没有限制的。不过我尝试了一下即便不使用use开头建立的自定义hook其实也是正常运行的。react 约定以use开头是为了经过一些自动检查工具来来校验 这些use开头的hook内部是否违反了 hook的使用规则。不过仍是最好遵循使用use开头,驼峰命名这样一个约定。

而后调用 usestate 建立一个 自定义hook内部的状态, 传入了 navigator online 初始化当前的网络状态,而且在自定义 hook 内部拥有了本身的状态。

接着在使用 useeffect 钩子内,咱们须要监听网络状态的变化,联网或者断网的监听方法只须要在组件建立时调用一次便可,因此咱们在传递useeffect的第二个参数时,只传递了一个空的数组,这样它就只会在 didmount 时被调用一次。

一样的在销毁组件时,咱们也须要清除掉这些监听事情。在 effect 钩子的返回函数中清理掉这两个监听方法便可

最后咱们将 当前的状态值 做为自定义hook的返回值,这样就完成了一个监听网络状态的自定义hook。

调用 useOnline 自定义hook

binlive前端开发,web开发,node,vue,react,webpack

咱们在一个最简单的函数组件中引入这个hook。 而后在函数组件内咱们调用这个自定义hook,hook会返回当前的网络状态,这是一个很简单的函数组件,咱们只渲染当前的网络状态。

当咱们渲染出组件时候他渲染出了当前的网络状态, 当咱们切换为离线状态时,能够看组件被从新渲染出了当前的网络状态值。这样,当咱们建立出一个自定义hook时 就能够在不少地方调用直接复用这段代码,达到了逻辑复用的目的。

不过你们可能以为这不就是抽象一个方法么? 好像没有自定义hook 咱们也能够把一些方法抽象到一些工具函数中, 写一些 helper 或者 utils来完成这些功能 。 可是实际上是不同的,自定义的hook强大之处在于拥有状态,当状态值改变时,它能够触发调用组件的从新渲染,并且自定义hook能够跟随着组件的生命周期,在不一样的生命钩子阶段,咱们能够处理一些事件。

自定义生命周期Hook

binlive前端开发,web开发,node,vue,react,webpack
咱们在以前也用hook的useeffect API 实现过模拟 class 组件中的生命周期,可是若是函数组件多了,咱们可能在不少组件中都会用到这些生命周期,虽然使用 useeffect 实现几个生命周期也只有几行代码,可是在每一个组件里都写一遍显然不符合逻辑复用的原则。

咱们能够选择本身去 实现 一个 useDidMount 自定义hook,实现起来也很是简单。只须要在钩子内部调用 useeffect hook,将它的第一个参数传递为咱们自定义钩子的回调函数参数,将第二个可选的参数设为一个空数组,这样就只会在 didmount时候被调用一次。

开源社区里的自定义Hook

binlive前端开发,web开发,node,vue,react,webpack
这是github上star较多的一个 use hook 项目, 叫 react-use,该项目到目前为止已经有 8.7K的star。

这个开源项目帮咱们收集了一系列已经封装好的自定义hook,好比这些生命周期钩子,咱们只须要引入便可实现这些生命周期。包括一些 didMount, unmount, update 等钩子。

使用React Hook写的进度条组件

binlive前端开发,web开发,node,vue,react,webpack

分享一个React Hook弧形进度条组件react-arc-progress,这个以前是一个es6方式写的插件,而后以前为了跟进 react hook,将它改形成了一个 react 组件。

  1. 由于使用了 react hooks 的api,因此只能使用在 react 版本 >= 16.8.0 版本
  2. 使用的typescript,若是一样使用了ts时,使用组件对参数以及方法会自动有类型推导提示
  3. 源码使用了tslint 以及 爱彼迎规范 进行了行代码风格校验

重复的造轮子确定是没有意义的,因此要添加一些独特的功能让它变得更不同些:

  • 大部分圆环进度条都是一个整圆,这个组件是能够是弧形的,能够自由定义他的起始结束位置。
  • 能够传入一个数值,它会随着进度条的增加自动增长。
  • 能够传入一个或多个文字节点
  • 能够定义进度条的厚度,让内层超过外层
  • 进度条背景能够贴图
  • 动画速度能够经过一个速度阀值去调整,也能够固定一个动画时长

GitHub地址,请给个star吧 ◔ ‸◔