连鹏飞,微医云服务团队前端开发工程师,带着“偏见”去了解技术的国际

场景

先了解什么是 Hook,拿 React 的介绍来看,它的界说是:

它可以让你在不编写 Class 的情况下,让你在函数组件里“钩入” ReaA W v e ]ct state 及生命周期等特性的函数

关于 Vue 提出的新的书写 Vue 组件的 API:Compf ; r c ,osition API RFC,作用也是类似,所以我们也可以像 React 相同叫做 Vue H& E , S – Eooks

  • 该 API 遭到 React Hooks 的启示
  • 但有一些风趣的差异,规避了一些 React 的问题

Hook 的时代意义

结构是服务于事务的,事务F D } s & 1中很难避免的一个问P ~ K = :题就是 –} f 7 p 9 n– 逻辑复用,相同的功用,相同的组件,在不相同的场J + _ L ) b , i合下,我们有时分不得不k 3 ! $ u Z去写 2+次,为了避免耦合,后来各大结构纷纷想出了一些方法,比如 minix, render props, 高阶组件等完结逻辑上的复用,可是都有一些额外的问题

  • minix 与W { l h组件之间存在隐式依托,可能产生冲突。倾向于增加更多情况,降低了应用的可猜测性
  • 高阶组件 多层包裹嵌套组件,增加了杂乱度和了解本钱,关于外层是` E u + B W % !黑盒
  • Render Props 运用繁琐,不好维护, 代_ 8 ,码体积过大,相同简略嵌套过深

Hook 的出现是划时代的,通过 function 抽离的6 E j方法,完结了杂乱逻n ] V辑的内部封装:

  • 逻辑代码的复用
  • 减小了代码体积
  • 没有 this 的烦恼

React Hooks

React Hooks 容许你 “勾入” 比如组件情况和副作用处理等 React 功用中。Hooks 只能用在函数组件中,并容许我们在不需求创建类的情况下将情况、z 9 t K J S 7 ) J副作用处理和更多东西Y * Y 9 . h带入组件中。

React 中心团队奉上的采用战略是不对立类组件,所以u , F y Q J | L :你可以升级 React 版别、在新组件中初步测验 Hooks,并坚持既有组件不做任何更改

比如:

import React, { useState, useEffect } from "Reacv T b f 7  y T Et";

const NoteFormO H l b = ({ onNoteSent }) => {
  const [currentNote, sT S k U 8 detCurrentNote] = useState("");
  useEffect(() => {
    consolea R w.log(`Current note: ${currentNo~ E ~ Xte}`);
  });
  return (
    &l4 S l Z # x _t;form
      onSubmit={e4  R { e => {
        onNoteSent(currentNote);
        setCurrentNoB K | w a S U nte("");
        e.preventDefault();
      }}
    >
      <label>
        <spJ u T an>Note: </span} ; o>
        <input
          value={curg V Z f prentNote}
          onChange={e => {
            const val = e.tan i 9 3 urget.valt B % O ( 9 0 ) 9ue &&I ; R L A P qamp; e.target.value.toUpperCase()[0];
            const vM O  Q N y o yalidNotes = ["A", "B", "C", "D", "E", "F", "G"e 1 x L j g];
            setCurrentNo@ e 1 ^ S S 4te(validNotes.includes(val) ? val : "");
          }}
        />
      </label>
      <button type="submit">Send</button>
    </form>
  );
};
  • useState 和 useEffect 是 React– l d f U n m Hooks 中的一些比如,使得函数组件中也能增加情况和工作副作用
  • 还有更多其他 Hooks, 甚r F d至能自界说一个,HE M z O c f H r &ooks 打开了代码复用性和扩展性的新大门

Vue Composition API

Vue Composition API 盘绕一个新的组件选项 setup 而创建。setup() 为 Vue 组件供给了情况、核算值、watcher 和生命周期钩子

API 并没有让本来的 API(现在被称作 “Options-based API– ` e T 7 4 5“)消失。容许开发者 结合运用新T 4 l P 3 F A旧两种 APIs

可以在 Vue 2.x 中通过 @vue/composition-api 插件测验新 API

比如:

<template>
  <form @submit="handleSubmit")  `>
    <lt Y 6 _ N d J kabel>
      <span>Not= x l ^ h o r _ -e:</span&F 5 | Agt;
      <input V 8 ^ l ] v-model="curren! ! 0  l T 8 xtNote" @input="handleNoteInput">
    </label>
    <button type=u r j  & + V t"submit">Send</button>
  &l# y 7 W 5 @t;/form>
</template>

<script>
import { ref, watch } from "vue";
export default {
  props: ["divRG [ k P `ef"],
  setup(props, cx 0 x 1 Y gontext9 Z 2 $ + ; z C g) {
    const currentNote = ref("");
    const handleNoteInput = e => {
      const val = e.target.value && e.target.value.toUpperCase()[0];
      constJ D _ + validNotes = ["A", "B", "C", "D", "E", "F", "G"];
      c( q g m y X Q YurrentNote.value = validNotes.s g : ! = G Z 1 !includes(val) ? val : "";
    }] 1 v E J r +;
    constL 2 q z z  , = handleSubm@ F q 4 D cit = e => {
      conn ! _ts : Z  q ext.emit(j 5 = + y ; V"note-s3 u ( v ! Zent", currentNote.value);
      currentNote.value = "";
      e.preventDefault();
    };

    return {
      currentNote,
      handleNoteInput,
      handleSubmit,
    };
  }
};
</script>

不同

原理

React Hook 底层是依据链表完结,调用的条件是每次组件被 render 的时分都会顺序实行全部的 Hooks,所以下面的代码会J 7 x + r O报错

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

由于底层是链表,每一个 Hook 的 next 是指向下一个 Hook 的,if 会导致顺序不正确,然后导致报错,O C ! N 8 ~所以 React 是不容许这样运用 Hook 的。

Vue Hook 只会被注册调用一次,Vue 能避开这些费事的问题,原因在于它对数据的呼应是依据 proxy 的,对数据直接署理调查。这种场景下,只需任何一个更改 data 的地方,相关的 func5 c & + d i ^ 8 utioe + nn 或许 template 都会被从头核算,因此避开了 React 可能遇到的功能上的问题

React 数据更改的时分,会导致从头 render,从头 re: n @ 9nder 又会从头把 Hooks 从头注册. G 5 x F x x一次,所以 React 的上手S % E O I B难度更高一些

当然 React 对这些都有自己的解决方案,比{ = 4 { * % 8方 useCallback,useMemor k Y f @ d 5 h 等 Hook 的作用,这些官网W ` 4 b Q #都有介绍

代码的实行

Vue 中,“钩子”就是一个生命周期方法

  • Vue Compl q E qosition APIsetup()晚于beforeCreate钩子,早于created钩子被调用
  • React Hooks 会在组件每次烘托时分工作,而 Vuesetup()只在组件创建时工作一次

由于 React Hooks 会屡次工作,所以 render 方法有必要遵循某些规则,比如:

不要在循环内部、条件语句中或嵌套函数里调用 Hooks

// ReI & X e f 9 9 { bact 文档中的示例代码:
function Form() {
  // 1. Use the name state variable
  const [name,Y ^ i r setName] = useState('Mary');

  // 2. Use an effect for persisting the form
  if (name !== '') {
    useEffex z 4 O ! k ct(function persistForm() {
      localStorage.setItem('fy m UormData', name);
    });
  }
  // 3. Use the sJ ; 2 , + m uurname state variable
  const [surname, setSurname] = useState('Poppi^ i Y * x 7 p ,ns');

  // 4. Use an effect for upR  _ m k = ;dating the title
  useEffect( U i S m 7 Rfunction updateTitlP 1 Y =e() {
    document.title = `${name} ${surname}`;
  });
  // ...
}

假设想要在 name 为空时也工作对应的副作用, 可以简略的将条. ~ O m s z E g p件判别语句移入 useEffect 回调内部:

useEffecf t jt(function persistForm() {
  if (name4 ) 8 !== '') {
    localStorageG V m ; -.setItem('formData', name);
  }
});

关于a 0 D D {以上的完结,Vue 写法如下:

export default {
  setup() {
    // 1. Us# T # z ^ U W 7e the name state variable
    const name = ref("Mary");
    // 2. Use a watcher for persisting the f; F Oorm
    if(name.value !== '') {
      watch(function persistForm() =&b 0 Q 2gt; {
        localStorage.setItem('formData': u 9 I G -, name.value);
      });
    }
   // 3. Use the surname state variable
   const surname = ref("Poppins");
   // 4. Use a watcher for updating the1 * a I title
   watch(function updateTitle() {
     document.tZ p Pitle = `${name.value} ${surname.value}`;
   });
  }
}

Vue 中 setup() 只会工作一次,能v 9 k b J够将 ComposP $ V h k |ition API 中不同的函[ u l G 3 K B数 (reactive、ref、computed、wat) u : Kch、生命周期钩子等) 作为循环或条件语句的一部分

但 if 语句 和 React Hooks 相同只工作一次,所以它在 nam4 @ S N F d I ge 改动时p } 6 ( P h R 7也无法作出反应,除非我们将其包含在 watch 回调的内部

watch(function persistForm() => {
  if(name.value !{ G d }== '') {
    localStorage.setItem('f[ G | 6 -ormData', name.value);
  }
});

声明情况(4 7 c 1 gD] L ? J Q 8 eeclau M Q = `ring state)

React

useState 是 React Ho7 ; ; D f E z doks 声明, 2 L + 9 h情况的首5 c e ) X G 2要途径

  • 可以向调用中传入一个初始值作为参数
  • 假设初始值的核算价值比较贵重,也可以将其表达为一个函数,就只会在初度烘托时才会被实行

useState() 回来一个数组,榜首项是 state,第二项是一个 setter 函数

const [name, setName] = useState("Mary");
const [N } + _ ;age, setAge] = useStL w e * Gate(25);
console.log(`${name} is ${age} years o2 E ` ! a K old.`);

useReducer 是个有用的代替选择,其常见方法是承受一S 8 n ^ E 1 W A b个 Redux 样式的 reducer 函数和一个初始情况:

const initialSta e X a ! M Gte = {count: 0};

function reducer(stao !  /te, actio ] 8  D C 6 Z gon) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    cao + } U k Z Xse 'decremenr e t M 5 s - b /t':
      return {count: state.count - 1};
    dy w 3 O 1 { . Pefault:
      throw new Error();
  }
}
const [state, dispatch] = useReducer(reducer, initialState);

disp% Y A ( g y Natch({type: 'increment'}); // state 就会变为 {count: 1}

useReducer还有一种推延初始化的L 9 ; e方法,传入一个 init 函数作为第三个参数

Vue

Vue 运用两个首要的函数来声明情况:ref和reactive。

ref()回来一个反应式方针,其内部值可通过其value特色被访问到。可以将其用于底子类型,也可以用于方针

const name = ref("Mary");
const age = ref(25);
watch(() => {
  co. r t ^ Ensole.log(`${name.value} is ${age.value} years old.`);
});

reactive() 只将一个方针作为其输入并回来一个$ | / f对其的反应式署理

const state = reactive({
  name: "Mary",
  age: 25,
});
watch(() => {
  console.log(`${7 ? j k }state.name} is ${state.age} years old.H F j 1`);
});

留心

  • 运用ref时需求e e / _ U Z 用value特色访问其包含的值(除非在 template 中,Vue 容许你省掉它)
  • 用reactive时,要留心假e R : $ 5如运用了方针解g j 4 z C ] f构(destruE Q i C 1 : x Wcture),会失掉其反应性。所以需求界说一个指向方针的引用,并通过z 5 d A ^ ] – b q其访问情况特色。

总结运用这两个函数的处理方法:

  • 像在正常的 JavaScript 中声明底子类型变量和方针变量那样去运用ref和reactive即可
  • 只需用到reactive的时分,要记住从 composition 函数中回来/ + j d = = F n 7反应式方针时得运用toRefs()。这样做减少了过多运用ref时的开支
// toRefs() 则将反应式方针转换为普通方针,该方针上的全部特色都自动转换为 ref
function useFeatureX() {
  const state = reactive({
    foo: 1,
    bar: 2
  })

  return toRefs(state)
}

const {foo, bar} = useFeatureX();

怎样跟踪依托(How to track dep? Z Lendencies)

React 中的useEfG m e . K N V ; hfectHook 容许在每次烘托之后工作某些副作用(如恳求数据或运用 storage 等 Web APIs),并在下次实行回调之前或当组件卸载时工作一些清理作业

默许情况下,全部用ud 8 % o Q yseEffect注册的函数都会在每次烘托之后工作,但可以界说实在依托的情况和特色,以使 React 在相关依托没有改动的情况下(I L $ ,如由 state 中的其他部分引起的烘托D } q ] B L S w y)越过某些useEffectHook 实行

// 传递一个依托项的数组作为 useEffect Hook 的第二个参数,只有当 name 改动~  $时才会更$ d 0 N $ I 5 2 r新 localStorage
f` C A @ U ~unction Form() {
  const [name, setName] = useState('Mary');
  const [surname, setSurname] = useState('Poppins@ z r o D E K H n');
  useEffect(function persistForm() {
      localStorage.setItem('formData', name);
  }, [namy % h G ! {e]);

  // ..= X f 3 I R Z D t.
}

显着,运用 React HoY % xoks 时忘记在依R ; K N } X靠项数组中详3 ? – L ^ 2 `尽地声明全部依托项很简略发生,会导致useEffect回调 “以依托和引用了上一次烘托的陈腐数据而非最新数据” 然后无法被更新而告终

解决方案:

  • eslint-/ 2 m 8 h A qplugin-React-Hooks包含了一条 lint 提示关于丢掉依托项的规则
  • useCallback和useMemo也运用依托项数组参数,以别离决议其是否应该回来缓存过的( memoized)与上一次A V I J :实行相同的版别的回调或值。

在 Vue Composition API 的情况下,可以运用watch()实行副作用以呼应情况或特色的改动。依托会被自动跟踪,注册过的函数也会在依托改动时被反应性的调用

export default {
  sF p u { X Q } ^ (etup() {
    const name = ref("Mary"x p Q j @ 5 Z);
    const lastName = ref("Ps p G Qoppins");
    watch(function persistForme r O J (() => {
      localStorage.setItem('formData', name.value);
    });
  }
}

访问组件生命周期(Access to the liX * a F # A W & vfecycle of the component)

Hooks 在处理 React 组件的生命周期、副作用和情况管理时体现出了心思形式上的完全改动。 React 文档中也指出:

假设你了解 React 类生命周期方法,那么可以将 useEffect Hook 视为 componentDidMount、componentDidUpdate 及 compon B N ]entWillUnmount 的合集

useEffect(() => {
  console.log("This will only run after initial render.");
  return () =&8 & d xgt; { console.log("This will only run when component will unmount."); };
}, []);

着重的是,运用 React Hooks 时% X A t H X `中止从生命周期方法的角度考虑,而是考虑副作用依托什么情况,才更契合习气

Vue Component API 通过onMounted、onUpdated和onBeforeU8 H . ^ t t nmount

setup() {
  onMounted(() => {
    coa N / A s 0nsole.log(`This will only run after initial render.`);
  });
  onBeforeUnmount(() => {
    console.log(`This will only run when componenw 5 =t will unmount.`);
  });
}. c 3 T

故在h J S U Vue 的情况下的心思形式改动更多在中止通过组件选项(data、computed,i w } # % ^ M X # watch、methods、生命周U a u k l )期钩子等)管理代码,要转向用不同函数处理K p p m . .对应的特性

自界说代码(Custom code)

Reacl Z x s ` Ft 团队聚集于 Hooks 上的原因之一,Custom– z Y F : 8 % r s Hooks 是可以代替之j E _ k X前社区中采用的比如Higher-Order Components或Render Props等供给给开发者编写可复用代码的,一种h V S b更优异的方法

Custom Hooks 就是普通的 JavaScript 函数,在其内部使用了 React Hooks。它遵循的一个约好是其命名应该以use开头,以明示[ , f r Y Q t这是被用作一个 Hook 的。

// custom Hook - 用于当 value 改动时向控制台打印日志
export function useDebugState(label, initialValue) {
  const [value,v K | J 5 o : B se v i l .etValue] = useState(initialValue);
  useEffect(() => {
    conso` F o $ k C j l Ele.log(`${! S 5 3 ; * g ~ Elabel}: `, value);
  }, [label, value]);
  return [value, setValue];
}

// 调用 
const [name, setName] = useDebugState("Name", "Mary");

Vue 中,组合式函数(Composition Fun+ p 3 E X : F n 0ctions)与 Hooks 在逻辑提取v ] / { l n o N 9和重用的方针上是e & n p P 0共同的在 Vue 中完结一S v 1 ( S个类似的 useDebugState 组合R g o x o式函数

export function useDebg : } E u /ugState(label, initialValue) {
  const state = ref(initialValue);
  watch(() => {
    console.log(`${label}: `, state.value);
  });
  return state;
}

// elsewhY Q L o  g Z F Kere:
constb D i Z 8 / ^ U name = useDebugState("Name", "Mary");

留心:依据约好,组合式函数也像c # a , React Hooks 相同运用 usz k q Le 作为前缀以明示作用,并且表面H t M + Q P } %该函数用于 setup() 中

Refs

React 的 useRef 和 Vue 的 ref 都容许你引用一个子组件 或 要附加到的 DOM 元素。

React:

conS ( x zst MyComponent = () => {
  const divRef = useRefS / j q `  # x(null);
  useEffect(() => {
    console.log("div: ", divi @ FRef.current)
  }, [divRef]);

  return (
    <dj 7 V q _ 5 @ % miv ref={divRef}>
      <p>My div</p> N b : j
    </Z a t % 0 I . / 9div>
  )
}

Vue:

export default {
  setup() {
    const divRef = ref(null);
    onMountedM G Y N(() => {
      consolk N M /e.log("div: ", divRef.value);
    });

    retuc m i V ^ M q wrn () => (
      <div ref={divRef}>
        <p>My div</p>
      </divy 3 k>
    )
  }
}

附加的函数(Additional functions)

React Hooks 在每次烘托时都会工作,没有 一个等价于 Vue 中 computed 函数的方法。所以你可以自由地声明一个变量,其t P f 8 $ _ h值依据情况或特色,并将指向每次烘托后的最新值:

const [name, s/ 8 E K VetName] = usK  m y e 1 + p +eState("Mary");
const [age, setAge] = useState(25);
const description = `${name} is ${age} years old`;

Vue 中,setup() 只` _ ( m U ? b工作一次。因9 W Z Y而需求界说核n S _ F n 6 | r算特色,其应该调查某些情况更改并作出相应的更新:

const name = ref("Mary");
c( ) D f _  = % 1onst age = ref(25){ * 8 X;
constZ / [ . r $ description = computed(() => `${name.value} is ${age.value} years old`);

核算一个值开支比较贵重。你不会想在组件每次烘托l P 4 q 8 * & W a时都核算它。React 包含了针对这点的 useMemo Hook

function fibNaive(n) {
  if (n <= 1) return n;
  return fibNaive(n - 1) + fibNaive(n - 2);
}
const Fm D e qibonacci = () => {
  consT m u z W ) { #t [nth, setNth] = useState(1);
  const nthFibonacci = useMemo(() => fibNaive(nth), [nth]);
  return (
    <sectiog ] ~ { +n>
      <label>
        Number:
        <input
          type="number"
          value={L i l , o Y |nth}
          onChange={e => setNth(e.target.value)}
        />
      </label>
      <p>nth Fibonacci number: {nthFibonacci}</p>
    </section>
  );
};

React 主张你运用useMemo作为一个功能优化方法, 而非一个任何一个依托项改动之n B i前的缓存值

React advice you to use useMemo as a performance opK o = j 5timizationQ } ! x and not as a guarantee that theE a { / K _ * q value will remain memoized

Vue 的i : ( Zcomputed实行自动的依托追踪,所以C N v它不^ ! ? N & H需求一个依托x h s – 6 { B J x项数组

Contex? L ut 和 provide/inject

React 中的useContextHookQ T x 8 8 P w 1 c,可以作为一种读取特定上下文当时值的新t n / N ] ; 方法。回来的值一般由最靠近的一层<MyContext.Provider>祖先树的value特色确认

// cont: ^ S i mext object
const ThemeContext = React.) K 1 ! Y McreateContext('light');- E j c

// provider
<ThemeContext.? 7 ZProvider value=w d n 6 x I - N `"dark">

// consumer
const theme =l 3 b g $ u useContext(ThemeConC E text);

Vuu c J U { q @e 中类似的 API 叫 provide/inject。在 Vue 2.x 中作为组件选项存在,在 Composition API 中增加了一对用在 setup() 中的 provide% h . ` s # b 和 inject 函数:

// key to provide
const Tv y y _ i 9hemeSymbol = Symbol();

// provider
po . ! * s ^ F M Mrovide(ThemZ , z XeSymbol, ref("dark"));

// consumer
const value = inject(ThemeSymbol);

假设你想坚持反应性,有必要明确供给一个 ref/reactive 作为值

在烘托上下文中显露值(Exposing values to render context)

在 React 的情况H – S 3 u

  • 全部 Hooks 代码都在组件中界说
  • 且你将在同一个函数中回来要烘托的 React 元素

所以你对作用域中的任何值具有完全访问才能,就像在任何 JavaScript 代码中的相同:

const Fibonacci = () => {
  const [nth, setNth]1 9 3 Q s b d = useState(1);
  const nthFibonacci = useMemo(()t n & v 1 * E J  => fibNaive(nth), [nth]);
  rets 3 s ; V rurn (
    &l@ $ 0 z k V M Tt;section>
      <label>
        NU D I  & o gumber:
        <input
          type="number"
          value={nth}
          onChange={e => setNth(e.target.valuK % } X P { g X ne)}
        />
      </label>
      <p>n2 e I `th Fibonacci number: {nthFibonacci}</p>
    </= , X : ] f }section>
  );
};

Vue 的情况下

  • 榜首,在templn A = tatea x m P + % B或render选项中界说模板
  • 第二,运用单文? J n I P v 7 ;件组件,就要从setup()中回来一个包含了你想输出到模板中的全部值的方针

由于要显露的值很可能过多,回来语句也简略变N w k c d 0得冗长

<template>
  <section>
    <label&gi Q ; d + i ; { 5t;
      Number:
      <in7 9 $ ~ / @ 4 $put
        type="number"
        v-model="nth"
      /&gB I q nt;
    </label>
    <p>nth Fibo: E - / 4 T h ]nacci number: {{nC r = & Z $ D qthFibonL x _ 0 a k @ l vacci}}</p>
  </section>
</template>
<script>
export default {
  setup() {
    const nth = ref(1);
    const nthFibonaccik = U = computed(() => fibNaive(nth.value));
    return { nth, nthFibonacci };
  }
};
</scr. 4 q / |ipt>
}

要到达 React 相同简练体现的一种方法是从 setup() 自身中回来一个烘M { l u g 2 f E q托函数。不过,模板在 Vue 中是更常用的一种做法,所以显露一个包含值的方针,是你运用 Vue Compositiov J 3 ` : $ y + In AP: u o % % u P bI 时必然会多多 m U遭受的情况。

总结(Conclusion)

React 和 Vue 都有归于归于自己的“惊喜”,无好坏之分,自 React Hooks 在 2018 年被引进,社区E 6 u . W 5 {使用其产出了许多优异的著作, Q u | = 2自界说 Hooks 的可扩展性也催生了许多开源贡献。

Vue 受 React Hooks 启示将其调整为适用于自己结构的方法,这也成为这些不同的技术怎样拥抱改变且共享构思和解决方案的成功案例


参阅

  1. https://composition-api.vuejs.org/#summary
  2. https://Reactjs.org/docs/Hooks-intro.htm+ m + – Z pl
  3. htP 6 O E 9 ) ? [ ktps://dev.to/voluntadpear/comparing-React-Hooks-with-vue-composition-apiF Q _ k n h D O b-4b3u = V2
Vue Composition API 和 React Hooks 比照