<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          對(duì)比 React Hooks 和 Vue Composition API

          共 11524字,需瀏覽 24分鐘

           ·

          2020-08-17 20:36


          • 場(chǎng)景
          • Hook 的時(shí)代意義
          • React Hooks
          • Vue Composition API
          • React Hooks?vs?Vue Composition API
          • 總結(jié)

          場(chǎng)景

          先理解什么是hook,拿react的介紹來(lái)看,它的定義是:

          它可以讓你在不編寫(xiě) class 的情況下,讓你在函數(shù)組件里“鉤入” React state 及生命周期等特性的函數(shù)

          對(duì)于 Vue 提出的新的書(shū)寫(xiě) Vue 組件的 API:Composition API RFC,作用也是類似,所以我們也可以像react一樣叫做 vue hooks

          • 該 API 受到 React Hooks 的啟發(fā)
          • 但有一些有趣的差異,規(guī)避了一些react的問(wèn)題

          hook的時(shí)代意義

          框架是服務(wù)于業(yè)務(wù)的,業(yè)務(wù)中很難避免的一個(gè)問(wèn)題就是 -- 邏輯復(fù)用,同樣的功能,同樣的組件,在不一樣的場(chǎng)合下,我們有時(shí)候不得不去寫(xiě)2+次,為了避免耦合,后來(lái)各大框架紛紛想出了一些辦法,比如 minix, render props, 高階組件等實(shí)現(xiàn)邏輯上的復(fù)用,但是都有一些額外的問(wèn)題

          • minix 與組件之間存在隱式依賴,可能產(chǎn)生沖突。傾向于增加更多狀態(tài),降低了應(yīng)用的可預(yù)測(cè)性
          • 高階組件 多層包裹嵌套組件,增加了復(fù)雜度和理解成本,對(duì)于外層是黑盒
          • Render Props 使用繁瑣,不好維護(hù), 代碼體積過(guò)大,同樣容易嵌套過(guò)深
          • ...

          hook的出現(xiàn)是劃時(shí)代的,通過(guò)function抽離的方式,實(shí)現(xiàn)了復(fù)雜邏輯的內(nèi)部封裝:

          • 邏輯代碼的復(fù)用
          • 減小了代碼體積
          • 沒(méi)有this的煩惱

          React Hooks

          React Hooks 允許你 "勾入" 諸如組件狀態(tài)和副作用處理等 React 功能中。Hooks 只能用在函數(shù)組件中,并允許我們?cè)诓恍枰獎(jiǎng)?chuàng)建類的情況下將狀態(tài)、副作用處理和更多東西帶入組件中。

          React 核心團(tuán)隊(duì)奉上的采納策略是不反對(duì)類組件,所以你可以升級(jí) React 版本、在新組件中開(kāi)始嘗試 Hooks,并保持既有組件不做任何更改

          例子:

          import React, { useState, useEffect } from "react";
          const NoteForm = ({ onNoteSent }) => { const [currentNote, setCurrentNote] = useState(""); useEffect(() => { console.log(`Current note: ${currentNote}`); }); return ( onSubmit={e => { onNoteSent(currentNote); setCurrentNote(""); e.preventDefault(); }} > Note: 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 : ""); }} /> );};
          • useState 和 useEffect 是 React Hooks 中的一些例子,使得函數(shù)組件中也能增加狀態(tài)和運(yùn)行副作用
          • 還有更多其他 hooks, 甚至能自定義一個(gè),hooks 打開(kāi)了代碼復(fù)用性和擴(kuò)展性的新大門(mén)

          Vue Composition API

          Vue Composition API 圍繞一個(gè)新的組件選項(xiàng) setup 而創(chuàng)建。setup()?為 Vue 組件提供了狀態(tài)、計(jì)算值、watcher 和生命周期鉤子

          API 并沒(méi)有讓原來(lái)的 API(現(xiàn)在被稱作 "Options-based API")消失。允許開(kāi)發(fā)者 結(jié)合使用新舊兩種 APIs

          可以在 Vue 2.x 中通過(guò)?@vue/composition-api?插件嘗試新 API

          例子:


          React Hooks?vs?Vue Composition API

          原理

          React hook 底層是基于鏈表實(shí)現(xiàn),調(diào)用的條件是每次組件被render的時(shí)候都會(huì)順序執(zhí)行所有的hooks,所以下面的代碼會(huì)報(bào)錯(cuò)

          function App(){  const [name, setName] = useState('demo');  if(condition){    const [val, setVal] = useState('');      }}

          因?yàn)榈讓邮擎湵恚恳粋€(gè)hook的next是指向下一個(gè)hook的,if會(huì)導(dǎo)致順序不正確,從而導(dǎo)致報(bào)錯(cuò),所以react是不允許這樣使用hook的。

          vue hook 只會(huì)被注冊(cè)調(diào)用一次,vue 能避開(kāi)這些麻煩的問(wèn)題,原因在于它對(duì)數(shù)據(jù)的響應(yīng)是基于proxy的,對(duì)數(shù)據(jù)直接代理觀察。這種場(chǎng)景下,只要任何一個(gè)更改data的地方,相關(guān)的function或者template都會(huì)被重新計(jì)算,因此避開(kāi)了react可能遇到的性能上的問(wèn)題

          react數(shù)據(jù)更改的時(shí)候,會(huì)導(dǎo)致重新render,重新render又會(huì)重新把hooks重新注冊(cè)一次,所以react的上手難度更高一些

          當(dāng)然react對(duì)這些都有自己的解決方案,比如useCallback,useMemo等hook的作用,這些官網(wǎng)都有介紹

          代碼的執(zhí)行

          Vue 中,“鉤子”就是一個(gè)生命周期方法

          • Vue Composition API?的?setup()?晚于?beforeCreate?鉤子,早于?created?鉤子被調(diào)用
          • React hooks 會(huì)在組件每次渲染時(shí)候運(yùn)行,而 Vue?setup()?只在組件創(chuàng)建時(shí)運(yùn)行一次

          由于 React hooks 會(huì)多次運(yùn)行,所以 render 方法必須遵守某些規(guī)則,比如:

          不要在循環(huán)內(nèi)部、條件語(yǔ)句中或嵌套函數(shù)里調(diào)用 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 為空時(shí)也運(yùn)行對(duì)應(yīng)的副作用, 可以簡(jiǎn)單的將條件判斷語(yǔ)句移入 useEffect 回調(diào)內(nèi)部:

          useEffect(function persistForm() {  if (name !== '') {    localStorage.setItem('formData', name);  }});

          對(duì)于以上的實(shí)現(xiàn),Vue 寫(xiě)法如下:

          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() 只會(huì)運(yùn)行一次,可以將 Composition API 中不同的函數(shù) (reactive、ref、computed、watch、生命周期鉤子等) 作為循環(huán)或條件語(yǔ)句的一部分

          但 if 語(yǔ)句 和 react hooks 一樣只運(yùn)行一次,所以它在 name 改變時(shí)也無(wú)法作出反應(yīng),除非我們將其包含在 watch 回調(diào)的內(nèi)部

          watch(function persistForm() => {  if(name.value !== '') {    localStorage.setItem('formData', name.value);  }});

          聲明狀態(tài)(Declaring state)

          react

          useState?是 React Hooks 聲明狀態(tài)的主要途徑

          • 可以向調(diào)用中傳入一個(gè)初始值作為參數(shù)
          • 如果初始值的計(jì)算代價(jià)比較昂貴,也可以將其表達(dá)為一個(gè)函數(shù),就只會(huì)在初次渲染時(shí)才會(huì)被執(zhí)行

          useState() 返回一個(gè)數(shù)組,第一項(xiàng)是 state,第二項(xiàng)是一個(gè) setter 函數(shù)

          const [name, setName] = useState("Mary");const [age, setAge] = useState(25);console.log(`${name} is ${age} years old.`);

          useReducer?是個(gè)有用的替代選擇,其常見(jiàn)形式是接受一個(gè) Redux 樣式的 reducer 函數(shù)和一個(gè)初始狀態(tài):

          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?就會(huì)變?yōu)?{count: 1}

          useReducer?還有一種?延遲初始化?的形式,傳入一個(gè) init 函數(shù)作為第三個(gè)參數(shù)

          Vue

          Vue 使用兩個(gè)主要的函數(shù)來(lái)聲明狀態(tài):ref 和 reactive。

          ref()?返回一個(gè)反應(yīng)式對(duì)象,其內(nèi)部值可通過(guò)其 value 屬性被訪問(wèn)到。可以將其用于基本類型,也可以用于對(duì)象

          const name = ref("Mary");const age = ref(25);watch(() => {  console.log(`${name.value} is ${age.value} years old.`);});

          reactive()?只將一個(gè)對(duì)象作為其輸入并返回一個(gè)對(duì)其的反應(yīng)式代理

          const state = reactive({  name: "Mary",  age: 25,});watch(() => {  console.log(`${state.name} is ${state.age} years old.`);});

          注意

          • 使用?ref?時(shí)需要 用?value?屬性訪問(wèn)其包含的值(除非在 template 中,Vue 允許你省略它)
          • 用 reactive 時(shí),要注意如果使用了對(duì)象解構(gòu)(destructure),會(huì)失去其反應(yīng)性。所以需要定義一個(gè)指向?qū)ο蟮囊茫⑼ㄟ^(guò)其訪問(wèn)狀態(tài)屬性。

          總結(jié)使用這兩個(gè)函數(shù)的處理方式:

          • 像在正常的 JavaScript 中聲明基本類型變量和對(duì)象變量那樣去使用?ref?和?reactive?即可
          • 只要用到 reactive 的時(shí)候,要記住從 composition 函數(shù)中返回反應(yīng)式對(duì)象時(shí)得使用 toRefs()。這樣做減少了過(guò)多使用 ref 時(shí)的開(kāi)銷
          // toRefs() 則將反應(yīng)式對(duì)象轉(zhuǎn)換為普通對(duì)象,該對(duì)象上的所有屬性都自動(dòng)轉(zhuǎn)換為 reffunction useFeatureX() {  const state = reactive({    foo: 1,    bar: 2  })   return toRefs(state)} const {foo, bar} = useFeatureX();

          如何跟蹤依賴(How to track dependencies)

          React 中的?useEffect?hook 允許在每次渲染之后運(yùn)行某些副作用(如請(qǐng)求數(shù)據(jù)或使用 storage 等 Web APIs),并在下次執(zhí)行回調(diào)之前或當(dāng)組件卸載時(shí)運(yùn)行一些清理工作

          默認(rèn)情況下,所有用?useEffect?注冊(cè)的函數(shù)都會(huì)在每次渲染之后運(yùn)行,但可以定義真實(shí)依賴的狀態(tài)和屬性,以使 React 在相關(guān)依賴沒(méi)有改變的情況下(如由 state 中的其他部分引起的渲染)跳過(guò)某些?useEffect?hook 執(zhí)行

          // 傳遞一個(gè)依賴項(xiàng)的數(shù)組作為?useEffect?hook 的第二個(gè)參數(shù),只有當(dāng)?name?改變時(shí)才會(huì)更新?localStoragefunction Form() {  const [name, setName] = useState('Mary');  const [surname, setSurname] = useState('Poppins');  useEffect(function persistForm() {      localStorage.setItem('formData', name);  }, [name]);
          // ...}

          顯然,使用 React Hooks 時(shí)忘記在依賴項(xiàng)數(shù)組中詳盡地聲明所有依賴項(xiàng)很容易發(fā)生,會(huì)導(dǎo)致?useEffect?回調(diào) "以依賴和引用了上一次渲染的陳舊數(shù)據(jù)而非最新數(shù)據(jù)" 從而無(wú)法被更新而告終

          解決方案:

          • eslint-plugin-react-hooks?包含了一條 lint 提示關(guān)于丟失依賴項(xiàng)的規(guī)則
          • useCallback?和?useMemo?也使用依賴項(xiàng)數(shù)組參數(shù),以分別決定其是否應(yīng)該返回緩存過(guò)的( memoized)與上一次執(zhí)行相同的版本的回調(diào)或值。

          在 Vue Composition API 的情況下,可以使用 watch()?執(zhí)行副作用以響應(yīng)狀態(tài)或?qū)傩缘母淖儭R蕾嚂?huì)被自動(dòng)跟蹤,注冊(cè)過(guò)的函數(shù)也會(huì)在依賴改變時(shí)被反應(yīng)性的調(diào)用

          export default {  setup() {    const name = ref("Mary");    const lastName = ref("Poppins");    watch(function persistForm() => {      localStorage.setItem('formData', name.value);    });  }}

          訪問(wèn)組件生命周期(Access to the lifecycle of the component)

          Hooks 在處理 React 組件的生命周期、副作用和狀態(tài)管理時(shí)表現(xiàn)出了心理模式上的完全轉(zhuǎn)變。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."); };}, []);

          強(qiáng)調(diào)的是,使用 React Hooks 時(shí)停止從生命周期方法的角度思考,而是考慮副作用依賴什么狀態(tài),才更符合習(xí)慣

          Vue Component API?通過(guò)?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 的情況下的心理模式轉(zhuǎn)變更多在停止通過(guò)組件選項(xiàng)(data、computed, watch、methods、生命周期鉤子等)管理代碼,要轉(zhuǎn)向用不同函數(shù)處理對(duì)應(yīng)的特性

          自定義代碼(Custom code)

          React 團(tuán)隊(duì)聚焦于 Hooks 上的原因之一,Custom Hooks 是可以替代之前社區(qū)中采納的諸如?Higher-Order Components?或?Render Props?等提供給開(kāi)發(fā)者編寫(xiě)可復(fù)用代碼的,一種更優(yōu)秀的方式

          Custom Hooks 就是普通的 JavaScript 函數(shù),在其內(nèi)部利用了 React Hooks。它遵守的一個(gè)約定是其命名應(yīng)該以 use 開(kāi)頭,以明示這是被用作一個(gè) hook 的。

          // custom hook - 用于當(dāng) value 改變時(shí)向控制臺(tái)打印日志export function useDebugState(label, initialValue) {  const [value, setValue] = useState(initialValue);  useEffect(() => {    console.log(`${label}: `, value);  }, [label, value]);  return [value, setValue];}
          // 調(diào)用 const [name, setName] = useDebugState("Name", "Mary");

          Vue 中,組合式函數(shù)(Composition Functions)與 Hooks 在邏輯提取和重用的目標(biāo)上是一致的在 Vue 中實(shí)現(xiàn)一個(gè)類似的 useDebugState 組合式函數(shù)

          export function useDebugState(label, initialValue) {  const state = ref(initialValue);  watch(() => {    console.log(`${label}: `, state.value);  });  return state;}
          // elsewhere:const name = useDebugState("Name", "Mary");

          注意:根據(jù)約定,組合式函數(shù)也像 React Hooks 一樣使用 use 作為前綴以明示作用,并且表面該函數(shù)用于 setup() 中

          Refs

          React 的 useRef 和 Vue 的 ref 都允許你引用一個(gè)子組件 或 要附加到的 DOM 元素。

          React:

          const MyComponent = () => {  const divRef = useRef(null);  useEffect(() => {    console.log("div: ", divRef.current)  }, [divRef]);
          return (

          My div

          )}

          Vue:

          export default {  setup() {    const divRef = ref(null);    onMounted(() => {      console.log("div: ", divRef.value);    });
          return () => (

          My div

          ) }}

          附加的函數(shù)(Additional functions)

          React Hooks 在每次渲染時(shí)都會(huì)運(yùn)行,沒(méi)有 一個(gè)等價(jià)于 Vue 中 computed 函數(shù)的方法。所以你可以自由地聲明一個(gè)變量,其值基于狀態(tài)或?qū)傩裕⒅赶蛎看武秩竞蟮淖钚轮担?/p>

          const [name, setName] = useState("Mary");const [age, setAge] = useState(25);const description = `${name} is ${age} years old`;

          Vue 中,setup() 只運(yùn)行一次。因此需要定義計(jì)算屬性,其應(yīng)該觀察某些狀態(tài)更改并作出相應(yīng)的更新:

          const name = ref("Mary");const age = ref(25);const description = computed(() => `${name.value} is ${age.value} years old`);

          計(jì)算一個(gè)值開(kāi)銷比較昂貴。你不會(huì)想在組件每次渲染時(shí)都計(jì)算它。React 包含了針對(duì)這點(diǎn)的?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 (    
          Number: type="number" value={nth} onChange={e => setNth(e.target.value)} />

          nth Fibonacci number: {nthFibonacci}

          );};

          React 建議你使用?useMemo?作為一個(gè)性能優(yōu)化手段, 而非一個(gè)任何一個(gè)依賴項(xiàng)改變之前的緩存值

          React advice you to use useMemo as a performance optimization and not as a guarantee that the value will remain memoized

          Vue 的?computed?執(zhí)行自動(dòng)的依賴追蹤,所以它不需要一個(gè)依賴項(xiàng)數(shù)組

          Context 和 provide/inject

          React 中的 useContext hook,可以作為一種讀取特定上下文當(dāng)前值的新方式。返回的值通常由最靠近的一層??祖先樹(shù)的?value?屬性確定

          // context objectconst ThemeContext = React.createContext('light');
          // provider
          // consumerconst theme = useContext(ThemeContext);

          Vue 中類似的 API 叫?provide/inject。在 Vue 2.x 中作為組件選項(xiàng)存在,在 Composition API 中增加了一對(duì)用在 setup() 中的?provide 和 inject?函數(shù):

          // key to provideconst ThemeSymbol = Symbol();
          // providerprovide(ThemeSymbol, ref("dark"));
          // consumerconst value = inject(ThemeSymbol);

          如果你想保持反應(yīng)性,必須明確提供一個(gè) ref/reactive 作為值

          在渲染上下文中暴露值(Exposing values to render context)

          在 React 的情況下??

          • 所有 hooks 代碼都在組件中定義
          • 且你將在同一個(gè)函數(shù)中返回要渲染的 React 元素

          所以你對(duì)作用域中的任何值擁有完全訪問(wèn)能力,就像在任何 JavaScript 代碼中的一樣:

          const Fibonacci = () => {  const [nth, setNth] = useState(1);  const nthFibonacci = useMemo(() => fibNaive(nth), [nth]);  return (    
          Number: type="number" value={nth} onChange={e => setNth(e.target.value)} />

          nth Fibonacci number: {nthFibonacci}

          );};

          Vue 的情況下??

          • 第一,在?template?或?render?選項(xiàng)中定義模板
          • 第二,使用單文件組件,就要從?setup()?中返回一個(gè)包含了你想輸出到模板中的所有值的對(duì)象

          由于要暴露的值很可能過(guò)多,返回語(yǔ)句也容易變得冗長(zhǎng)

          }

          要達(dá)到 React 同樣簡(jiǎn)潔表現(xiàn)的一種方式是從 setup() 自身中返回一個(gè)渲染函數(shù)。不過(guò),模板在 Vue 中是更常用的一種做法,所以暴露一個(gè)包含值的對(duì)象,是你使用 Vue Composition API 時(shí)必然會(huì)多多遭遇的情況。

          總結(jié)(Conclusion)

          React 和 Vue都有屬于屬于自己的“驚喜”,無(wú)優(yōu)劣之分,自 React Hooks 在 2018 年被引入,社區(qū)利用其產(chǎn)出了很多優(yōu)秀的作品,自定義 Hooks 的可擴(kuò)展性也催生了許多開(kāi)源貢獻(xiàn)。

          Vue 受 React Hooks 啟發(fā)將其調(diào)整為適用于自己框架的方式,這也成為這些不同的技術(shù)如何擁抱變化且分享靈感和解決方案的成功案例

          最后, 希望大家早日實(shí)現(xiàn):成為前端高手的偉大夢(mèng)想!

          歡迎交流~

          參考
          1. 《Composition API RFC》-?https://composition-api.vuejs.org/#summary
          2. 《React hooks》-?https://reactjs.org/docs/hooks-intro.html
          3. 《Comparing React Hooks with Vue Composition API》-?https://dev.to/voluntadpear/comparing-react-hooks-with-vue-composition-api-4b32




          推薦閱讀




          我的公眾號(hào)能帶來(lái)什么價(jià)值?(文末有送書(shū)規(guī)則,一定要看)

          每個(gè)前端工程師都應(yīng)該了解的圖片知識(shí)(長(zhǎng)文建議收藏)

          為什么現(xiàn)在面試總是面試造火箭?

          瀏覽 59
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  麻豆精品在线 | 爱液视频网页版免费 | 熟女性爱网 | αv天堂αv电影亚洲ωa | 韩导航激情亚洲丁香幼导航 |