hi~ 豆皮粉儿. 又见面啦~

React Hooks release 曾经有很长一段时间了,Vue3.0 也带来了 Function based API,都咱们提供了新的模式来复用和形象逻辑,那这两者有什么区别的,松宝写代码给大家带了解读。

作者: 松宝写代码

一、前言

React Hooks 是 React16.8 引入的新个性,反对在类组件之外应用 state、生命周期等个性。

Vue Function-based API 是 Vue3.0 最重要的 RFC (Requests for Comments),将 2.x 中与组件逻辑相干的选项以 API函数 的模式从新设计。

目录:

  • React Hooks

    • React Hooks是什么
    • useState Hook
    • useEffect Hook
    • React Hooks 引入的起因以及设计准则
    • React Hooks 应用准则及其背地的原理
  • Vue3.0 Function-based API

    • 根本用法
    • 引入的起因以及解决的问题
  • React Hooks 与 Vue3.0 Function-based API 的比照

二、React Hooks

React Hooks 是什么?

援用官网的一段话:

从概念上讲,React 组件更像是函数。而 Hooks 则拥抱了函数,同时也没有就义 React 的精力准则。Hooks 提供了问题的解决方案,无需学习简单的函数式或响应式编程技术。

另外,Hooks 是100%向后兼容的,也是齐全可选的。

React Hooks 提供了三个根底 Hook : useStateuseEffectuseContext,其余 Hooks 可参考React Hooks API。

useState Hook

上面是一个实现计数器性能的类组件示例:

import React from 'react';class Example extends React.Component {  constructor (props) {    super(props)    this.state = {      count: 0    }  }  render () {    return (      <div>        <p>You clicked {this.state.count} times</p>        <button onClick={() => this.setState({count: this.state.count + 1)}}>Click me</button>       </div>    )  }}

需要很简略,设定初始值为0,当点击按钮时,count 加 1。

当应用 useState Hook 实现上述需要时:

import React, { useState } from 'react';function Example() {  const [count, setCount] = useState(0);  return (    <div>      <p>You clicked {count} times</p>      <button onClick={() => setCount(count + 1)}>        Click me      </button>    </div>  );}

其中 useState Hook 做了哪些事件呢?

  1. 在调用 useState 办法时,定义了一个state变量count,它与类组件中的this.state性能完全相同。对于一般变量,在函数退出后即隐没,而state中的变量会被 React 保留。
  2. useState 办法只接管一个参数,那就是初始值。useState 办法一次只定义一个变量,如果想在state中存储两个变量,只须要调用两次 useState() 即可。
  3. useState 的返回值是一个由 以后state 以及 更新state的函数 组成的数组。这也是采纳 数组解构 形式来获取的起因。

在应用 Hooks 实现的示例中,会发现 useState 让代码更加简洁了:

  • 获取state:类组件中应用 this.state.count ,而应用了 useSatet Hook 的函数组件中间接应用 count 即可。
  • 更新state:类组件中应用 this.setState() 更新,函数组件中应用 setCount() 即可。

这里抛出几个疑难,在解说原理的中央会进行具体解释:

  • React 是如何记住 useState 上次值的?
  • React 是如何晓得 useState 对应的是哪一个组件?
  • 如果一个组件内有多个 useState,那从新渲染时 useState 的取值程序又是怎么确定的?

useEffect Hook

在讲 useEffect 之前,先讲一下 React 的副作用。

在 React 中,数据获取、订阅或者手动批改DOM等操作,均被称为 '副作用',或者 '作用' 。

而 useEffect 就是一个 Effect Hook ,为函数组件增加操作副作用的能力,能够把它看作是类组件中的componentDidMountcomponentDidUpdatecomponentWillUnmount三个周期函数的组合。

上面是一个对于订阅的例子:

import React from 'react';class Example extends React.Component {  constructor (props) {    super(props)    this.state = {      isOnline: null    }  }  componentDidMount() {    ChatAPI.subscribeToFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  componentDidUpdate(prevProps) {    // 当 friend.id 变动时进行更新    if (prevProps.friend.id !== this.props.friend.id) {      // 勾销订阅之前的 friend.id      ChatAPI.unsubscribeFromFriendStatus(        prevProps.friend.id,        this.handleStatusChange      );      // 订阅新的 friend.id      ChatAPI.subscribeToFriendStatus(        this.props.friend.id,        this.handleStatusChange      );    }  }  componentWillUnmount() {    ChatAPI.unsubscribeFromFriendStatus(      this.props.friend.id,      this.handleStatusChange    );  }  handleStatusChange = (state) => {    this.setState({      isOnline: status.isOnline    });  }  render () {    return (      <div>        {this.state.isOnline === null ? 'Loading...' :          (this.state.isOnline ? 'Online' : 'Offline')        }      </div>    )  }}

从上述的代码中不难发现,存在肯定的反复代码,逻辑不得不拆分在三个生命周期外部。另外因为类组件不会默认绑定 this ,在定义 handleStatusChange 时,还须要为它 绑定this

这里补充一点,对于类组件,须要审慎看待 JSX 回调函数中的 this,类组件默认是不会绑定 this 的,上面提供几种绑定 this 的办法:

  1. 通常的做法是将事件处理函数申明为 class 中的办法,如:constructor外部 this.handleClick = this.handleClick.bind(this)
  2. 在 onClick 外部应用箭头函数, 如:onClick={e=>this.handleClick(id, e)},留神:该办法在每次渲染时都会创立不同的回调函数。在大多数状况下,没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额定的从新渲染。
  3. 在 onClick 外部应用 bind 绑定, 如:onClick={this.handleClick.bind(this, e)}
  4. 应用 class fields 绑定:如:handleClick = (e) => {}

这也是 React 引入 Hooks 的其中一个起因。

上面让咱们看一下 useEffect Hook 是如何实现上述需要的:

import React, { useState, useEffect } from 'react';function Example(props) {  const [isOnline, setIsOnline] = useState(null);  useEffect(() => {    const handleStatusChange = (status) => {      setIsOnline(status.isOnline)    }    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);    return () => {      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);    };  }, [props.friend.id]);  return (    <div>      {isOnline === null ? 'Loading...' :        (isOnline ? 'Online' : 'Offline')      }    </div>  )}

在上述例子中,你可能会对 useEffect Hook 产生以下疑难:

  1. useEffect 做了什么?首先,接管了一个函数,而 React 会保留这个函数(称为'effect'),并且在执行 DOM 更新后调用这个函数,即增加订阅。
  2. [props.friend.id] 参数设置了什么?

    • 默认状况下,useEffect 接管一个函数作为第一个参数,在不设置第二个参数时,每一次渲染都会执行 effect,这有可能会导致性能问题。
    • 为了解决这个问题,可增加第二个参数,用来管制什么时候将会执行更新,这时候就相当于 componentDidUpdate 做的事件。
    • 如果想只运行一次 effect ,即仅在组件挂载和卸载时执行,第二个参数可传递一个空数组[]
  3. 在 useEffect 中为什么要返回一个函数呢?这是一个可选的操作,每一个 effect 都能够返回一个革除函数。在 React 中,有一些副作用是须要革除的,比方 监听函数、定时器等,这时候就须要为 effect 减少一个返回函数,React 会在组件卸载的时候执行革除操作。

Hooks 所提供的性能远不止这些,更多具体的介绍能够查阅官网文档。

React Hooks 引入的起因以及设计准则

React Hooks 具体解决了什么问题呢? React 为什么要引入这一个性呢?

次要有以下三点起因:

  1. 在组件之间复用状态逻辑很艰难。

    • React 并没有提供将可复用行为附加到组件的路径,个别比拟常见的办法是采纳 render props高阶组件 解决。
    • React Hooks 反对 自定义Hook,能够将状态逻辑从组件中提出,使得这些逻辑可进行独自测试、复用,在无需批改组件构造的状况下即可实现状态逻辑复用。点击查看自定义Hook应用阐明。
  2. 简单组件变得难以了解。

    • 每个生命周期函数外部逻辑简单、性能不繁多,互相关联的需要被拆分在各个生命周期中,而不相干的代码却须要在同一个周期外部进行整合。
    • 为了解决这个问题,React Hooks 将组件中互相关联的局部拆分成更小的函数,引入了 Effect Hook,如上述 useEffect 的示例,正是解决了这个问题。
  3. 难以了解的class。

    • this绑定
    • 打包尺寸,函数通过ES export导出,能够借助 tree-shaking 把没用到的函数移除;压缩混同,类的属性和办法没法压缩
    • class热重载不稳固

React Hooks 设计准则

次要有以下四点:

  1. 优雅高效的复用状态逻辑
  2. 无 class 困扰
  3. 具备 class 已有的能力
  4. 性能繁多的副作用

上面咱们依据几个例子来感触 React Hooks 具体是如何体现的。

1、优雅高效的复用状态逻辑

在之前,状态逻辑的复用个别是采纳 Mixins APIRender PropsHOC实现,然而因为Render Props 与 HOC 自身也是组件,状态逻辑的复用也是通过封装组件的模式来实现,仍难以避免组件多层嵌套的问题,也比利于后续的了解与保护。

在 React Hooks 中,提供了 自定义Hook 来实现状态逻辑的复用。

比方 在聊天程序中,应用订阅获取好友的状态:

import React, { useState, useEffect } from 'react';function useOnline(id) {  const [isOnline, setIsOnline] = useState(null);  useEffect(() => {    function handleStatusChange (state) {      setIsOnline(status.isOnline)    }    ChatAPI.subscribeToFriendStatus(id, handleStatusChange);    return () => {      ChatAPI.unsubscribeFromFriendStatus(id, handleStatusChange);    };  }, [id]);  return isOnline;}// 应用 自定义Hookfunction Example(props) {  const isOnline = useOnline(props.friend.id);    if (isOnline === null) {    return 'Loading...';  }  return isOnline ? 'Online' : 'Offline';}

能够看到 useOnline 组件的逻辑是与业务齐全无关的,它只是用来增加订阅、勾销订阅,以获取用户的状态。

总结:

  • 数据起源分明,之间依据函数返回值取得
  • 代码量少,更易保护
  • 防止反复创立组件带来性能损耗

留神

  • 自定义Hook 是一个函数,名称必须以 'use' 结尾,这是一个约定,如果不遵循,React 将无奈自动检测是否违反了 Hook 规定。
  • 在函数的外部能够调用其余 Hook,然而请确保只在顶层无条件地调用其余Hook。
  • React 会依据名称来检测 Hook 是否违反了规定。
  • 自定义 Hook 是一种重用状态逻辑的机制,每次应用时,外部 state 与副作用是齐全隔离的。

2、无 class 困扰

上面咱们将依据一个具体例子 实现依据点击事件,管制节点的展现或者暗藏的需要,来对 Render PropsHOCHooks的实现形式做简略比照。

应用 Render Props 实现

Render Props 是指一种在 React 组件之间应用一个值为函数的 prop 共享代码的简略技术。

创立 VisibilityHelper

import React from 'react';import PropTypes from 'prop-types';class VisibilityHelper extends React.Component {  constructor(props) {    super(props);    this.state = {      isDisplayed: props.initialState || false,    };  }  hide = () => {    this.setState({      isDisplayed: false,    });  }  show = () => {    this.setState({      isDisplayed: true,    });  }  render() {    return this.props.children({      ...this.state,      hide: this.hide,      show: this.show,    });  }}VisibilityHelper.propTypes = {  initialState: PropTypes.bool,  children: PropTypes.func.isRequired,};export default VisibilityHelper;

VisibilityHelper 的应用:

import React from 'react';import VisibilityHelper from 'VisibilityHelper';function ButtonComponent() {  return (    <VisibilityHelper initialState={true}>      {        ({          isDisplayed,          hide,          show        }) => (            <div>              {                isDisplayed ?                  <button onClick={hide}>Click to hide</button>                  :                  <button onClick={show}>Click to display</button>              }            </div>          )      }    </VisibilityHelper>  );}

<ButtonComponent>组件中,咱们应用了一个带有函数prop<VisibilityHelper>组件,实现了代码复用。

应用 HOC 实现

高阶组件,是 React 中复用组件逻辑的一种高级技巧,是一种基于 React 组合个性而造成的设计模式。

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

import React from 'react';function VisibilityHelper(WrappedComponent, initialState = false) {  return class extends React.Component {    constructor(props) {      super(props);      this.state = {        isDisplayed: initialState      };    }    hide = () => {      this.setState({        isDisplayed: false,      });    }    show = () => {      this.setState({        isDisplayed: true,      });    }    render() {      return <WrappedComponent        isDisplayed={this.state.isDisplayed}        show={() => this.show()}        hide={() => this.hide()}        {...this.props} />;    }  };}// 定义 按钮组件let ButtonComponent = ({ isDisplayed, hide, show }) => {  return (    <div>      {        isDisplayed ?          <button onClick={hide}>Click to hide</button>          :          <button onClick={show}>Click to display</button>      }    </div>  );}// 应用高阶组件,并设定默认值ButtonComponent = VisibilityHelper(ButtonComponent, true);export default ButtonComponent
在 React Hooks 中是如何实现上述逻辑的呢?
import React, { useState } from 'react';function ButtonComponent() {  const [isDisplayed, show] = useState(initialState || false)  return (    <div>      {        isDisplayed ?          <button onClick={() => show(false)}>Click to hide</button>          :          <button onClick={() => show(true)}>Click to display</button>      }    </div>  )}

从比照中能够发现,应用 Hooks 更简洁,且不须要在放心 this 绑定地问题。

3、具备 class 已有的能力

对于罕用的 class 能力,Hooks 曾经根本笼罩。

对于其余不常见的能力,官网给出的回应是:

目前临时还没有对应不罕用的 getSnapshotBeforeUpdate 和 componentDidCatch 生命周期的 Hook 等价写法,但咱们打算尽早把它们加进来。目前 Hook 还处于晚期阶段,一些第三方的库可能还临时无奈兼容 Hook。

4、性能繁多的副作用

通过文中的几个示例,应该能够理解到 useEffect Hook 便是设计用来解决副作用扩散、逻辑不繁多的问题。

在实在的利用场景中,可依据业务须要编写多个 useEffect。

React Hooks 应用准则

两条应用准则:

  1. 只在最顶层应用 Hooks,不能在循环、条件或嵌套函数中调用 Hooks。这是为了保障 Hooks 在每一次渲染中都依照同样的程序被调用。
  2. 只能在 React 的函数组件中或者 自定义Hook 中调用 Hooks。确保组件的状态逻辑在代码中清晰可见。

这两条准则让 React 可能在屡次的 useState 和 useEffect 调用之间放弃 hook 状态的正确。

React Hooks 背地的原理

之前的抛出的疑难:

  • React 是如何记住 useState 上次值的?
  • React 是如何晓得 useState 对应的是哪一个组件?
  • 如果一个组件内有多个 useState,那从新渲染时 useState 的取值程序又是怎么确定的?

在 React 中从编译到渲染成 Dom,都要经验这样的过程:JSX -> Element -> FiberNode -> Dom

Hooks 要想和一个函数组件进行绑定,
就要和这个转换过程的某个节点进行关联,因为 Hooks 只有在 render 过程中进行调用,很显著就只能关联到 FiberNode 上。

在 FiberNode 上有
一个属性 memoizedState,这个属性在 class 组件中对应最终渲染的 state。

class 组件的state个别是一个对象,在 函数组件中变成
一个链表,如 class 组件 memoizedState = {a: 1, b: 2} => 函数组件 memoizedState = {state: 1, next: {state: 2, next: ..}}

每个链表的节点都是一个 useState,从而将所有 Hooks 进行串联起来。不仅仅 State Hook,其它 Hook 也是通过 memoizedState 串联起来的。

第一次渲染后,通过 FiberNode 的 memoizedState 将所有 Hook 进行收集实现。

当执行 setState 进行组件更新时,从新执行函数组件,这时会从收集的 Hooks 中依照执行顺讯顺次取出,对于 State Hook 会进行计算将最新的值返回, Effect Hook 会在组件渲染完结后,先执行革除函数,再执行
副作用函数。

过程如图:

三、Vue3.0 Function-based API

首先提一下 Vue Function-based API 的降级策略。

vue官网提供了Vue Function API,反对Vue2.x版本应用组件逻辑复用机制。3.x无缝替换掉该库。

另外,Vue3.x公布反对两个不同版本:

  • 兼容版本:同时反对新 API 和 2.x 的所有选项
  • 规范版本:只反对新 API 和局部 2.x 选项

上面是一个根底例子:

import { value, computed, watch, onMounted } from 'vue'const App = {  template: `    <div>      <span>count is {{ count }}</span>      <span>plusOne is {{ plusOne }}</span>      <button @click="increment">count++</button>    </div>  `,  setup() {    // reactive state    const count = value(0)    // computed state    const plusOne = computed(() => count.value + 1)    // method    const increment = () => { count.value++ }    // watch    watch(() => count.value * 2, val => {      console.log(`count * 2 is ${val}`)    })    // lifecycle    onMounted(() => {      console.log(`mounted`)    })    // expose bindings on render context    return {      count,      plusOne,      increment    }  }}

Vue Function-based API 引入的起因及解决的问题

引入的起因,借用官网推出的一段话:

组件 API 设计所面对的外围问题之一就是如何组织逻辑,以及如何在多个组件之间抽取和复用逻辑。

其实也就是 React Hooks 引入时提到的:在组件之间复用状态逻辑很艰难。

在Vue2.0中,有一些常见的逻辑复用模式,如:Mixins高阶组件Renderless Components,这些模式均或多或少的存在以下问题:

  • 模版中的数据起源不清晰
  • 命名空间容易抵触
  • 性能问题,须要额定的组件实例嵌套来封装逻辑,带来不必要的性能开销等

Function-based API 受 React Hooks 的启发,提供一个全新的逻辑复用计划,且不存在上述问题。

四、React Hooks 与 Vue Function-based API 的比照

两者均具备基于函数提取和复用逻辑的能力。

React Hooks 在每次组件渲染时都会调用,通过隐式地将状态挂载在以后的外部组件节点上,在下一次渲染时依据调用程序取出。而 Vue 的响应式
机制使 setup() 只须要在初始化时调用一次,状态通过援用贮存在 setup() 的闭包内。这也是vue不受调用程序限度的起因。

The End