React与vue3功能点对比(一):状态 hooks

293 阅读5分钟

Vue3推出了composition api,并且官方支持了jsx,这使得vue3与react变得很相似,熟悉React的同学基本可以直接上手Vue3。我可能会写一个系列,去介绍React和vue3功能上的区分点。

状态hooks

什么是状态就不解释了,下面是用react和vue3分别实现一个自增的按钮的例子

// React
const Button = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(prev => prev+1)}>{count}</button>
}
// Vue
const Button = defineComponent({
  setup() {
    const count = ref(0);
    return () => <button onClick={() => count.value++}>{count.value}</button>
  }
})

这是一个简单的例子。React通过useState hook来维护count,vue通过ref api来维护count

来看看区别

immutable VS mutable

react的状态是immutable的,就是你不能直接修改它。在这个例子中,你只能通过setCount来修改count,下次渲染时,count的引用地址已经变了。

vue的状态是mutable的,你可以直接修改它,这个例子中,count.value++就可以工作了,下次渲染时,count的引用地址并没有变。

我个人是喜欢vue的mutable特性的。

vue对深层次的状态修改更加方便。

让我们把count升级为 bookList[0].store.count

// React
const Button = () => {
  const [bookList, setBookList] = useState([{
    store: {
      count: 0,
    }
  }]);
  const handleClick = useCallback(() => {
    setBookList(prev => prev.map((book, index) => index !== 0 ? book : ({
      ...book,
      store: {
        ...book.store,
        count: book.store.count + 1
      }
    })));
  }, []);
  return <button onClick={handleClick}>{count}</button>
}

React在不借助第三方工具的情况下,写起来很繁琐。

// Vue
const Button = defineComponent({
  setup() {
    const bookList = reactive([{
      store: {
        count: 0,
      }
    }])
    return () => (
      <button onClick={() => bookList[0].store.count++}>
        {count.value}
      </button>
    )
  }
})

Vue这里引入了另一个composition api: reactive. 相对react,写起来就简单的多了,直接赋值就行。

Vue可以同步的获取状态的最新值。

还是这个例子:

// React
const Button = () => {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => {
    setCount(count + 1);
    // or setCount(prev => prev + 1)
    console.log(count); // 这里输出的count值是滞后的,
  }, [count]);
  return <button onClick={handleClick}>{count}</button>
}

更新完count之后,再获取count的值,获取到的是旧的值。这是immutable+闭包的必然结果。

闭包导致里面变量的引用不会变,immutable导致变量的值也不会变,所以就不可能获取状态的最新值。

退而求其次,我们只能间接的获得,比如这种写法 setCount(prev => prev + 1),这里prev的值一定是最新的。但是它限制了,你只能获取一个状态的最新值,当组件变复杂时,这个上面的心智还是挺高的,你需要调整状态设定,调整执行顺序,设置临时变量等等。

于是有些同学喜欢把状态进行统一管理,比如用redux,这个问题也是原因之一。不过我一直是反对状态的中心化管理的,它其实就是把很多组件的逻辑代码写到了一起,使得代码变得难以阅读和维护。我们应该去中心化,也就是组件化。

// Vue
const Button = defineComponent({
  setup() {
    const count = ref(0);
    const handleClick = () => {
      count.value++;
      console.log(count.value); //这里是同步的
    }
    return () => <button onClick={handleClick}>{count.value}</button>
  }
})

vue中就完全没有这种烦恼了,状态一直是同步的。

no setup vs setup

我们看到vue的代码里面有一个setup函数,这个和react不一样,这个函数不是每次渲染都执行,而是在组件第一次渲染之前进行的初始化,只会执行一次。

与react的函数对应的其实setup函数的返回值,它也是一个函数,也就是渲染函数。它每次渲染都会执行。

有setup的好处显而易见,因为只执行一次,速度上一定会有所有提升。

React中你经常需要写useCallback,来缓存一个函数。在vue3中,这完全没有必要,所有定义在setup里面的函数都只会执行一次,不需要刻意缓存。

Proxy vs raw object

相对于React,Vue为了追踪状态的变化,使用了Proxy,来监听状态的使用和修改。这样的好处是,降低了在何时更新上的心智负担。所有都是由vue内部控制的。React中你可能就要经常考虑要不要加memo,要不要加useCallback,要不要加useMemo

但是Proxy也带来了一些编写上的麻烦:

ref状态要不停的写.value

由于Proxy只能作用在object上,无法应用到基础类型上。对于基本类型,vue3给出了ref作为解决方案,用一个对象的value属性来存储这些基本类型。但是带来的问题就是,引用和修改时要写.value。这确实很麻烦。

当然,vue3在一些特殊情况下是可以不用写.value的

  • 在模板中不用写.value。这点对我来说,没什么用,因为相对于jsx,我不是很喜欢模本,它的最大问题是,模板不能直接使用js中定义的变量,而是需要你把用到的变量return一下,这就不是很爽。
  • 通过reactive访问一个ref类型时,不需要写.value。
const count = ref(10);
const data = reactive({ count });
console.log(data.count) // 10
console.log(isRef(toRaw(data).count)) // true

不能任意解构

解构是指这种语法:const { name, id } = user

原因还是因为Proxy只能作用在object上面,所以当你解构出一个基本类型的变量时,这个变量就不可能是一个Proxy了,它也就失去了追踪的能力。

当你从reactive中解构出了object类型的变量,这个变量将仍然是一个proxy,它仍然可以响应追踪,只是过它不一定是正确的,因为这个变量有可能被上层删除。也因为这个原因,vue3的eslint规则有一条是不要解构props。

const data = reactive({
  a : {
    b: 1
  }
});
const { a } = data;
data.a = { b: 10 };
console.log(data.a === a) // false
// no change after click.
return () => <button onClick={() => a.b = 100}>{data.a.b}</button>

当然了,在一些条件下,是可以安全的进行结构的:比如不需要进行赋值的情况下,那么可以在return的render函数中解构,也可以在computed,watchEffect等中解构。

const data = reactive({
  count: 0
});
setInterval(() => {
  data.count++;
}, 1000);
return () => {
  const { count } = data;
  return <div>{count}</div>
}

总结

vue3使用了Proxy,它解决了React的一些问题,比如不容易获得状态的最新值,对深层次object修改麻烦,在处理更新上的心智负担等等。同时它也有一些不足,ref麻烦的.value,解构上存在潜在问题。