hi~ 豆皮粉儿. 又见面啦~javascript
React Hooks release 已经有很长一段时间了,Vue3.0 也带来了 Function based API,都咱们提供了新的模式来复用和抽象逻辑,那这二者有什么区别的,saucxs 给你们带了解读html
做者: 松宝写代码vue
React Hooks 是 React16.8 引入的新特性,支持在类组件以外使用 state、生命周期等特性。java
Vue Function-based API 是 Vue3.0 最重要的 RFC (Requests for Comments),将 2.x 中与组件逻辑相关的选项以 API函数 的形式从新设计。react
目录:git
引用官网的一段话:github
从概念上讲,React 组件更像是函数。而 Hooks 则拥抱了函数,同时也没有牺牲 React 的精神原则。Hooks 提供了问题的解决方案,无需学习复杂的函数式或响应式编程技术。编程
另外,Hooks 是100%向后兼容的,也是彻底可选的。设计模式
React Hooks 提供了三个基础 Hook : useState
、useEffect
、useContext
,其余 Hooks 可参考React Hooks API。api
下面是一个实现计数器功能的类组件示例:
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
作了哪些事情呢?
当前state
以及 更新state的函数
组成的数组。这也是采用 数组解构 方式来获取的缘由。在使用 Hooks 实现的示例中,会发现 useState 让代码更加简洁了:
this.state.count
,而使用了 useSatet Hook 的函数组件中直接使用 count
便可。this.setState()
更新,函数组件中使用 setCount()
便可。这里抛出几个疑问,在讲解原理的地方会进行详细解释:
在讲 useEffect
以前,先讲一下 React 的反作用。
在 React 中,数据获取、订阅或者手动修改DOM等操做,均被称为 '反作用',或者 '做用' 。
而 useEffect 就是一个 Effect Hook ,为函数组件添加操做反作用的能力,能够把它看做是类组件中的componentDidMount
、componentDidUpdate
、componentWillUnmount
三个周期函数的组合。
下面是一个关于订阅的例子:
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 的方法:
this.handleClick = this.handleClick.bind(this)
onClick={e=>this.handleClick(id, e)}
,注意:该方法在每次渲染时都会建立不一样的回调函数。在大多数状况下,没什么问题,但若是该回调函数做为 prop 传入子组件时,这些组件可能会进行额外的从新渲染。onClick={this.handleClick.bind(this, e)}
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 产生如下疑问:
[props.friend.id]
参数设置了什么?
componentDidUpdate
作的事情。[]
。Hooks 所提供的功能远不止这些,更多详细的介绍能够查阅官网文档。
React Hooks 具体解决了什么问题呢? React 为何要引入这一特性呢?
主要有如下三点缘由:
render props
和 高阶组件
解决。自定义Hook
,能够将状态逻辑从组件中提出,使得这些逻辑可进行单独测试、复用,在无需修改组件结构的状况下便可实现状态逻辑复用。点击查看自定义Hook使用说明。Effect Hook
,如上述 useEffect 的示例,正是解决了这个问题。主要有如下四点:
下面咱们根据几个例子来感觉 React Hooks 具体是如何体现的。
在以前,状态逻辑的复用通常是采用 Mixins API
、Render Props
或HOC
实现,可是因为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 组件的逻辑是与业务彻底无关的,它只是用来添加订阅、取消订阅,以获取用户的状态。
总结:
注意
'use'
开头,这是一个约定,若是不遵循,React 将没法自动检测是否违反了 Hook 规则。下面咱们将根据一个具体例子 实现根据点击事件,控制节点的展现或者隐藏的需求,来对 Render Props
、HOC
、Hooks
的实现方式作简单对比。
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>
组件,实现了代码复用。
高阶组件,是 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
复制代码
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 绑定地问题。
对于经常使用的 class 能力,Hooks 已经基本覆盖。
对于其余不常见的能力,官方给出的回应是:
目前暂时尚未对应不经常使用的 getSnapshotBeforeUpdate 和 componentDidCatch 生命周期的 Hook 等价写法,但咱们计划尽早把它们加进来。目前 Hook 还处于早期阶段,一些第三方的库可能还暂时没法兼容 Hook。
经过文中的几个示例,应该能够了解到 useEffect Hook 即是设计用来解决反作用分散、逻辑不单一的问题。
在真实的应用场景中,可根据业务须要编写多个 useEffect。
两条使用原则:
这两条原则让 React 可以在屡次的 useState 和 useEffect 调用之间保持 hook 状态的正确。
以前的抛出的疑问:
在 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 会在组件渲染结束后,先执行清除函数,再执行 反作用函数。
过程如图:
首先提一下 Vue Function-based API 的升级策略。
vue官方提供了Vue Function API,支持Vue2.x版本使用组件逻辑复用机制。3.x无缝替换掉该库。
另外,Vue3.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
}
}
}
复制代码
引入的缘由,借用官方推出的一段话:
组件 API 设计所面对的核心问题之一就是如何组织逻辑,以及如何在多个组件之间抽取和复用逻辑。
其实也就是 React Hooks 引入时提到的:在组件之间复用状态逻辑很困难。
在Vue2.0中,有一些常见的逻辑复用模式,如:Mixins
、高阶组件
、Renderless Components
,这些模式均或多或少的存在如下问题:
Function-based API 受 React Hooks 的启发,提供一个全新的逻辑复用方案,且不存在上述问题。
二者均具备基于函数提取和复用逻辑的能力。
React Hooks 在每次组件渲染时都会调用,经过隐式地将状态挂载在当前的内部组件节点上,在下一次渲染时根据调用顺序取出。而 Vue 的响应式 机制使 setup() 只须要在初始化时调用一次,状态经过引用储存在 setup() 的闭包内。这也是vue不受调用顺序限制的缘由。