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不受調用順序限制的緣由。