React hooks 和 Vue Composition API 的比较

React hooks vs Vue Composition API

Posted by Eric on November 1, 2020

React Hooks 的出现为我们提供了一种新的共享和复用逻辑的方式。而 Vue 3 Composition API 也是对这个概念的应用。这篇文章将会对这两种方式做一个对比。看一下 Vue 3 的响应式可变的(mutable reactivity)解决方案与 React 的不可变(immutability)的解决方案有哪些差别。

简单的组件状态

首先来看一下两者是如何来对 UI 状态来进行处理的

useState

import React, { useState } from "react";

export function Search() {
  const [searchValue, setSearchValue] = useState("Batman");

  return (
    <input
      value={searchValue}
      onChange={(e) => setSearchValue(e.target.value)}
    />
  );
}

使用 useState 可以得到一个数值和一个改变这个数值的方法,当对数值进行修改时,需要调用这个方法来进行改变。这也体现了 React 函数式组件的心智模型:函数式组件是 JavaScript 函数,每当有数据发生变更时,它们都将会执行。每当状态发生变化,React 会再次调用当前函数,丢弃上一次执行时的所有变量,同时只保留组件的状态。通过这种方式,函数块中的变量可以用 const 来声明,因为每一次渲染都会重新进行声明。

这也表示,在 React 中状态值时不可变的(immutable),它只能通过调用赋值函数来对状态来进行变更。

Vue 3

<script>
import { ref } from "vue";

export default {
  setup(props) {
    const search = ref("");
    return { search };
  },
};
</script>

<template>
  <div>
    <input v-model="search" />
    <input :value="search" @input="search = $event.target.value" />
  </div>
</template>

而 vue 的处理方式是声明一个 ref,这个 ref 函数返回一个对象,这个对象有一个 value 属性。vue 使用 reactivity 的方式对这个值进行了包装,使其变成了一个响应式的值。所以,我们可以直接对这个状态来进行赋值,同样,这使得我们可以只执行一次 setup,这个初始函数可以对所有的响应式状态值进行初始化,并将其暴露给 template。在 template 中,vue 会利用 @vue/compiler-sfc 将模板编译为 render function,并自动对 ref 值来进行解封。

Side Effects

在 React 中使用 useEffect

import {useState, useEffect} from 'react'
const API_KEY = 'xxx';

export default function useMovieApi() {
  const [search, setSearch] = useState('BatMan')
  const [movies, setMovies] = useState([])
  const [isLoading, setIsLoading] = useState(false)
  const fetchMovies = () => {
    const MOVIE_API_URL = `https://www.omdbapi.com/?s=${search}&apikey=${API_KEY}`;
    setIsLoading(true)
    fetch(MOVIE_API_URL)
      .then(response => response.json())
      .then(jsonResponse => {
        setMovies(jsonResponse.Search);
        setIsLoading(false)
      });
  };

  useEffect(fetchMovies, [search])

  return {
    isLoading,
    search,
    setSearch,
    movies
  };
};

在 React 中使用 useEffect 时,需要手动添加依赖,每当这个依赖的值发生变化时,useEffect 中传入的第一个参数才会再次执行。

在 Vue 3 中使用 watchEffect

import { reactive, watchEffect } from 'vue'
const API_KEY = 'xxx';

export const useMovieApi = () => {
  const state = reactive({
    search: 'Batman',
    loading: true,
    movies: []
  });

  watchEffect(() => {
    const MOVIE_API_URL = `https://www.omdbapi.com/?s=${state.search}&apikey=${API_KEY}`;

    fetch(MOVIE_API_URL)
      .then(response => response.json())
      .then(jsonResponse => {
        state.movies = jsonResponse.Search;
        state.loading = false;
      });
  });

  return state;
};

在 Vue 3 中,得益于响应式系统中内建的依赖收集功能,我们并不需要手动去设置依赖。

复用逻辑

如上面的例子,在 React 和 Vue 中,我们都可以将 hooks 抽离出来作为独立的文件来使用。相较于 mixins 和高阶组件等数据复用方式,数据的来源会变得给为清晰,而且也不会出现命名冲突等问题。但在 Vue 3 中,我们需要注意 ref 值,最好使用类型系统(TypeScript)来帮助我们来进行类型推断。

总结

React Hooks 和 Vue Composition API 的使用方式很好的体现了两者思维上的不同。一个是不可变的隐式进行数据修改,一个是可变的显式的响应式系统。

Vue 可以通过将数据包装成 ref,来使得我们可以直接对数据来进行修改。而 React 是通过重新触发 hooks 进而导致值的重新计算,所以每一次数据发生变更,hooks 都会执行一次。

关于两者的更多区别可以查看这个例子