关于react-hooks:谈谈React-Hooks-与Vue30-Function-based-API的区别

26次阅读

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

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;
}

// 应用 自定义 Hook
function 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

正文完
 0