React Hooks
vs
Vue Composition API
先理解什麼是hook,拿react的介紹來看,它的定義是:html
它可讓你在不編寫 class 的狀況下,讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數前端
對於 Vue 提出的新的書寫 Vue 組件的 API:Composition API RFC,做用也是相似,因此咱們也能夠像react同樣叫作 vue hooksvue
框架是服務於業務的,業務中很難避免的一個問題就是 -- 邏輯複用,一樣的功能,一樣的組件,在不同的場合下,咱們有時候不得不去寫2+次,爲了不耦合,後來各大框架紛紛想出了一些辦法,好比 minix, render props, 高階組件等實現邏輯上的複用,可是都有一些額外的問題react
hook的出現是劃時代的,經過function抽離的方式,實現了複雜邏輯的內部封裝:web
React Hooks 容許你 "勾入" 諸如組件狀態和反作用處理等 React 功能中。Hooks 只能用在函數組件中,並容許咱們在不須要建立類的狀況下將狀態、反作用處理和更多東西帶入組件中。api
React 核心團隊奉上的採納策略是不反對類組件,因此你能夠升級 React 版本、在新組件中開始嘗試 Hooks,並保持既有組件不作任何更改數組
例子:緩存
import React, { useState, useEffect } from "react";
const NoteForm = ({ onNoteSent }) => { const [currentNote, setCurrentNote] = useState(""); useEffect(() => { console.log(`Current note: ${currentNote}`); }); return ( <form onSubmit={e => { onNoteSent(currentNote); setCurrentNote(""); e.preventDefault(); }} > <label> <span>Note: </span> <input value={currentNote} onChange={e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; setCurrentNote(validNotes.includes(val) ? val : ""); }} /> </label> <button type="submit">Send</button> </form> ); }; 複製代碼
Vue Composition API 圍繞一個新的組件選項 setup 而建立。setup()
爲 Vue 組件提供了狀態、計算值、watcher 和生命週期鉤子性能優化
API 並無讓原來的 API(如今被稱做 "Options-based API")消失。容許開發者 結合使用新舊兩種 APIs框架
能夠在 Vue 2.x 中經過
@vue/composition-api
插件嘗試新 API
例子:
<template>
<form @submit="handleSubmit"> <label> <span>Note:</span> <input v-model="currentNote" @input="handleNoteInput"> </label> <button type="submit">Send</button> </form> </template> <script> import { ref, watch } from "vue"; export default { props: ["divRef"], setup(props, context) { const currentNote = ref(""); const handleNoteInput = e => { const val = e.target.value && e.target.value.toUpperCase()[0]; const validNotes = ["A", "B", "C", "D", "E", "F", "G"]; currentNote.value = validNotes.includes(val) ? val : ""; }; const handleSubmit = e => { context.emit("note-sent", currentNote.value); currentNote.value = ""; e.preventDefault(); }; return { currentNote, handleNoteInput, handleSubmit, }; } }; </script> 複製代碼
React Hooks
vs Vue Composition API
React hook 底層是基於鏈表實現,調用的條件是每次組件被render的時候都會順序執行全部的hooks,因此下面的代碼會報錯
function App(){
const [name, setName] = useState('demo'); if(condition){ const [val, setVal] = useState(''); } } 複製代碼
由於底層是鏈表,每個hook的next是指向下一個hook的,if會致使順序不正確,從而致使報錯,因此react是不容許這樣使用hook的。
vue hook 只會被註冊調用一次,vue 能避開這些麻煩的問題,緣由在於它對數據的響應是基於proxy的,對數據直接代理觀察。這種場景下,只要任何一個更改data的地方,相關的function或者template都會被從新計算,所以避開了react可能遇到的性能上的問題
react數據更改的時候,會致使從新render,從新render又會從新把hooks從新註冊一次,因此react的上手難度更高一些
固然react對這些都有本身的解決方案,好比useCallback,useMemo等hook的做用,這些官網都有介紹
Vue 中,「鉤子」就是一個生命週期方法
Vue Composition API
的
setup()
晚於 beforeCreate 鉤子,早於 created 鉤子被調用
因爲 React hooks 會屢次運行,因此 render 方法必須遵照某些規則,好比:
不要在循環內部、條件語句中或嵌套函數裏調用 Hooks
// React 文檔中的示例代碼:
function Form() { // 1. Use the name state variable const [name, setName] = useState('Mary'); // 2. Use an effect for persisting the form if (name !== '') { useEffect(function persistForm() { localStorage.setItem('formData', name); }); } // 3. Use the surname state variable const [surname, setSurname] = useState('Poppins'); // 4. Use an effect for updating the title useEffect(function updateTitle() { document.title = `${name} ${surname}`; }); // ... } 複製代碼
若是想要在 name 爲空時也運行對應的反作用, 能夠簡單的將條件判斷語句移入 useEffect 回調內部:
useEffect(function persistForm() {
if (name !== '') { localStorage.setItem('formData', name); } }); 複製代碼
對於以上的實現,Vue 寫法以下:
export default {
setup() { // 1. Use the name state variable const name = ref("Mary"); // 2. Use a watcher for persisting the form if(name.value !== '') { watch(function persistForm() => { localStorage.setItem('formData', name.value); }); } // 3. Use the surname state variable const surname = ref("Poppins"); // 4. Use a watcher for updating the title watch(function updateTitle() { document.title = `${name.value} ${surname.value}`; }); } } 複製代碼
Vue 中 setup() 只會運行一次,能夠將 Composition API 中不一樣的函數 (reactive、ref、computed、watch、生命週期鉤子等) 做爲循環或條件語句的一部分
但 if 語句 和 react hooks 同樣只運行一次,因此它在 name 改變時也沒法做出反應,除非咱們將其包含在 watch 回調的內部
watch(function persistForm() => {
if(name.value !== '') { localStorage.setItem('formData', name.value); } }); 複製代碼
useState
是 React Hooks 聲明狀態的主要途徑
useState() 返回一個數組,第一項是 state,第二項是一個 setter 函數
const [name, setName] = useState("Mary");
const [age, setAge] = useState(25); console.log(`${name} is ${age} years old.`); 複製代碼
useReducer
是個有用的替代選擇,其常見形式是接受一個 Redux 樣式的 reducer 函數和一個初始狀態:
const initialState = {count: 0};
function reducer(state, action) { switch (action.type) { case 'increment': return {count: state.count + 1}; case 'decrement': return {count: state.count - 1}; default: throw new Error(); } } const [state, dispatch] = useReducer(reducer, initialState); dispatch({type: 'increment'}); // state 就會變爲 {count: 1} 複製代碼
useReducer 還有一種 延遲初始化 的形式,傳入一個 init 函數做爲第三個參數
Vue 使用兩個主要的函數來聲明狀態:ref 和 reactive。
ref()
返回一個反應式對象,其內部值可經過其 value 屬性被訪問到。能夠將其用於基本類型,也能夠用於對象
const name = ref("Mary");
const age = ref(25); watch(() => { console.log(`${name.value} is ${age.value} years old.`); }); 複製代碼
reactive()
只將一個對象做爲其輸入並返回一個對其的反應式代理
const state = reactive({
name: "Mary", age: 25, }); watch(() => { console.log(`${state.name} is ${state.age} years old.`); }); 複製代碼
注意:
總結使用這兩個函數的處理方式:
// toRefs() 則將反應式對象轉換爲普通對象,該對象上的全部屬性都自動轉換爲 ref
function useFeatureX() { const state = reactive({ foo: 1, bar: 2 }) return toRefs(state) } const {foo, bar} = useFeatureX(); 複製代碼
React 中的 useEffect hook 容許在每次渲染以後運行某些反作用(如請求數據或使用 storage 等 Web APIs),並在下次執行回調以前或當組件卸載時運行一些清理工做
默認狀況下,全部用 useEffect 註冊的函數都會在每次渲染以後運行,但能夠定義真實依賴的狀態和屬性,以使 React 在相關依賴沒有改變的狀況下(如由 state 中的其餘部分引發的渲染)跳過某些 useEffect hook 執行
// 傳遞一個依賴項的數組做爲 useEffect hook 的第二個參數,只有當 name 改變時纔會更新 localStorage
function Form() { const [name, setName] = useState('Mary'); const [surname, setSurname] = useState('Poppins'); useEffect(function persistForm() { localStorage.setItem('formData', name); }, [name]); // ... } 複製代碼
顯然,使用 React Hooks 時忘記在依賴項數組中詳盡地聲明全部依賴項很容易發生,會致使 useEffect 回調 "以依賴和引用了上一次渲染的陳舊數據而非最新數據" 從而沒法被更新而了結
解決方案:
eslint-plugin-react-hooks
包含了一條 lint 提示關於丟失依賴項的規則
useCallback 和 useMemo
也使用依賴項數組參數,以分別決定其是否應該返回緩存過的( memoized)與上一次執行相同的版本的回調或值。
在 Vue Composition API 的狀況下,可使用 watch() 執行反作用以響應狀態或屬性的改變。依賴會被自動跟蹤,註冊過的函數也會在依賴改變時被反應性的調用
export default {
setup() { const name = ref("Mary"); const lastName = ref("Poppins"); watch(function persistForm() => { localStorage.setItem('formData', name.value); }); } } 複製代碼
Hooks 在處理 React 組件的生命週期、反作用和狀態管理時表現出了心理模式上的徹底轉變。 React 文檔中也指出:
若是你熟悉 React 類生命週期方法,那麼能夠將 useEffect Hook 視爲 componentDidMount、componentDidUpdate 及 componentWillUnmount 的合集
useEffect(() => {
console.log("This will only run after initial render."); return () => { console.log("This will only run when component will unmount."); }; }, []); 複製代碼
強調的是,使用 React Hooks 時中止從生命週期方法的角度思考,而是考慮反作用依賴什麼狀態,才更符合習慣
Vue Component API
經過 onMounted、onUpdated 和 onBeforeUnmount
:
setup() {
onMounted(() => { console.log(`This will only run after initial render.`); }); onBeforeUnmount(() => { console.log(`This will only run when component will unmount.`); }); } 複製代碼
故在 Vue 的狀況下的心理模式轉變動多在中止經過組件選項(data、computed, watch、methods、生命週期鉤子等)管理代碼,要轉向用不一樣函數處理對應的特性
React 團隊聚焦於 Hooks 上的緣由之一,Custom Hooks 是能夠替代以前社區中採納的諸如 Higher-Order Components 或 Render Props 等提供給開發者編寫可複用代碼的,一種更優秀的方式
Custom Hooks 就是普通的 JavaScript 函數,在其內部利用了 React Hooks。它遵照的一個約定是其命名應該以 use 開頭,以明示這是被用做一個 hook 的。
// custom hook - 用於當 value 改變時向控制檯打印日誌
export function useDebugState(label, initialValue) { const [value, setValue] = useState(initialValue); useEffect(() => { console.log(`${label}: `, value); }, [label, value]); return [value, setValue]; } // 調用 const [name, setName] = useDebugState("Name", "Mary"); 複製代碼
Vue 中,組合式函數(Composition Functions)與 Hooks 在邏輯提取和重用的目標上是一致的在 Vue 中實現一個相似的 useDebugState 組合式函數
export function useDebugState(label, initialValue) {
const state = ref(initialValue); watch(() => { console.log(`${label}: `, state.value); }); return state; } // elsewhere: const name = useDebugState("Name", "Mary"); 複製代碼
注意:根據約定,組合式函數也像 React Hooks 同樣使用 use 做爲前綴以明示做用,而且表面該函數用於 setup() 中
React 的 useRef 和 Vue 的 ref 都容許你引用一個子組件 或 要附加到的 DOM 元素。
React:
const MyComponent = () => {
const divRef = useRef(null); useEffect(() => { console.log("div: ", divRef.current) }, [divRef]); return ( <div ref={divRef}> <p>My div</p> </div> ) } 複製代碼
Vue:
export default {
setup() { const divRef = ref(null); onMounted(() => { console.log("div: ", divRef.value); }); return () => ( <div ref={divRef}> <p>My div</p> </div> ) } } 複製代碼
React Hooks 在每次渲染時都會運行,沒有 一個等價於 Vue 中 computed 函數的方法。因此你能夠自由地聲明一個變量,其值基於狀態或屬性,並將指向每次渲染後的最新值:
const [name, setName] = useState("Mary");
const [age, setAge] = useState(25); const description = `${name} is ${age} years old`; 複製代碼
Vue 中,setup() 只運行一次。所以須要定義計算屬性,其應該觀察某些狀態更改並做出相應的更新:
const name = ref("Mary");
const age = ref(25); const description = computed(() => `${name.value} is ${age.value} years old`); 複製代碼
計算一個值開銷比較昂貴。你不會想在組件每次渲染時都計算它。React 包含了針對這點的 useMemo hook
:
function fibNaive(n) {
if (n <= 1) return n; return fibNaive(n - 1) + fibNaive(n - 2); } const Fibonacci = () => { const [nth, setNth] = useState(1); const nthFibonacci = useMemo(() => fibNaive(nth), [nth]); return ( <section> <label> Number: <input type="number" value={nth} onChange={e => setNth(e.target.value)} /> </label> <p>nth Fibonacci number: {nthFibonacci}</p> </section> ); }; 複製代碼
React 建議你使用 useMemo 做爲一個性能優化手段, 而非一個任何一個依賴項改變以前的緩存值
React advice you to use useMemo as a performance optimization and not as a guarantee that the value will remain memoized
Vue 的 computed 執行自動的依賴追蹤,因此它不須要一個依賴項數組
React 中的 useContext hook,能夠做爲一種讀取特定上下文當前值的新方式。返回的值一般由最靠近的一層 <MyContext.Provider>
祖先樹的 value 屬性肯定
// context object
const ThemeContext = React.createContext('light'); // provider <ThemeContext.Provider value="dark"> // consumer const theme = useContext(ThemeContext); 複製代碼
Vue 中相似的 API 叫 provide/inject
。在 Vue 2.x 中做爲組件選項存在,在 Composition API 中增長了一對用在 setup() 中的 provide 和 inject
函數:
// key to provide
const ThemeSymbol = Symbol(); // provider provide(ThemeSymbol, ref("dark")); // consumer const value = inject(ThemeSymbol); 複製代碼
若是你想保持反應性,必須明確提供一個 ref/reactive 做爲值
在 React 的狀況下
因此你對做用域中的任何值擁有徹底訪問能力,就像在任何 JavaScript 代碼中的同樣:
const Fibonacci = () => {
const [nth, setNth] = useState(1); const nthFibonacci = useMemo(() => fibNaive(nth), [nth]); return ( <section> <label> Number: <input type="number" value={nth} onChange={e => setNth(e.target.value)} /> </label> <p>nth Fibonacci number: {nthFibonacci}</p> </section> ); }; 複製代碼
Vue 的狀況下
因爲要暴露的值極可能過多,返回語句也容易變得冗長
<template>
<section> <label> Number: <input type="number" v-model="nth" /> </label> <p>nth Fibonacci number: {{nthFibonacci}}</p> </section> </template> <script> export default { setup() { const nth = ref(1); const nthFibonacci = computed(() => fibNaive(nth.value)); return { nth, nthFibonacci }; } }; </script> } 複製代碼
要達到 React 一樣簡潔表現的一種方式是從 setup() 自身中返回一個渲染函數。不過,模板在 Vue 中是更經常使用的一種作法,因此暴露一個包含值的對象,是你使用 Vue Composition API 時必然會多多遭遇的狀況。
React 和 Vue都有屬於屬於本身的「驚喜」,無優劣之分,自 React Hooks 在 2018 年被引入,社區利用其產出了不少優秀的做品,自定義 Hooks 的可擴展性也催生了許多開源貢獻。
Vue 受 React Hooks 啓發將其調整爲適用於本身框架的方式,這也成爲這些不一樣的技術如何擁抱變化且分享靈感和解決方案的成功案例
最後, 但願你們早日實現:成爲前端高手的偉大夢想!
歡迎交流~