Vue和React的比较及技术选择标准 前篇

有些時候可以看到像是「使用React還是Vue,或者是第三個框架呢?」這樣的文章,作為同時使用React和Vue的人,我覺得兩者都有優點和缺點,即使要轉換到新的框架上,也應該在整理清楚這些方面後進行選擇。因此,我打算在比較整理React和Vue的同時,寫下自己的技術選擇標準。

首先,今天我想谈谈第一部分,关于我认为Vue比React更好的方面。

Vue有优势

自动判断依存关系

在React中,必须在useMemo或useCallback的第二个参数中明确注明受到影响的响应式值。如果漏写了这个参数,导致应该更新的值不会被更新,从而引发错误。

const [rate, setRate] = useState(3);
const star = useMemo(() => {
	return new Array(rate).fill('').join('');
	// ↓ 結果を算出するために必要なリアクティブ変数を依存配列に明記する必要
}, [rate]);

在这一点上,Vue 可以自动检测依赖关系并检测并重新计算具有反应性质的值的更新,这非常好。

const rate = ref(3);
const start = computed(() => {
	return new Array(rate).fill('').join('');
});

理解生命周期钩子和Watch的概念很容易。

20230815追记
经过收到的评论,我重新考虑了React和Vue的设计思想差异对本节的影响,所以我计划稍后重新整理并撰写一篇文章。我将在此时写下我当时的想法,仅供参考。

React的useEffect问题

为了避免上述依赖关系的遗漏,React提供了ESLint规则。
例如,如果忘记在依赖数组中添加使用在useMemo中使用的响应变量,就会出错。

const [rate, setRate] = useState(3);
const star = useMemo(() => {
	return new Array(rate).fill('').join('');
	// 依存配列の記入忘れ
}, []);

会输出类似以下的ESLint警告。

src\components\Hoge.tsx
  Line 32:8:  React Hook useMemo has a missing dependency: 'rate'. Either include it or remove the dependency array  react-hooks/exhaustive-deps

因此,尽管需要花费一些时间来手动输入依赖数组,但是利用ESLint可以防止在useMemo或useCallback中遗漏依赖数组。

然而,如果我们机械地将ESLint警告显示的变量添加到依存数组中,就有可能会导致意外执行useEffect处理的情况发生。

例如,假设我写了以下的处理过程:“当show标志位被设置时,执行calculate操作”。

const [show, setShow] = useState(false);
const [param, setParam] = useState({...});
const calculate = useCallback(() => {...}, [param]);

useEffect(() => {
	// showフラグが立った時にcalculate実行
	if (show) {
		calculate();
	}
}, [show])

在这段代码中,由于calculate没有出现在useEffect的依赖数组中,所以ESLint会发出警告。
为了消除这个警告,我们可以将calculate添加到useEffect的依赖数组中,这样警告就会消失。然而,这样做会导致在不希望的时机(例如上面的例子中,param的值发生变化的时候)也触发useEffect的处理。在某些情况下,甚至会引发无限循环。

同时,官方文档中提到,如果想要在组件生成时只执行一次特定操作,可以将 useEffect 的依赖数组设为空。然而,以下类似的代码仍会受到 ESLint 警告的影响。

const initialize = useCallback(() => {...}, []);
useEffect(() => {
  initialize();
// ↓ここにinitializeが入っていないことについてESLint警告が出る
}, []);

如果在代码中加入 eslint-disable-next-line 的注释,ESLint会忽略下一行的检查,所以我们会考虑采取这种方式来处理。但是,如果在项目组成员中养成了”当出现警告时,加入 eslint-disable-next-line 的注释”这样的习惯,这也是很危险的。

有人主张不应该加入忽略ESLint的注释,而是应该写出一些不会产生警告的程序。也有人持不同观点,认为无论如何还是会有写不好的情况。关于如何正确使用useEffect的讨论至今还在各处进行中,尚未得出唯一无二的最佳实践。

最近閱讀過的文章中提到:
「對於一般框架所提供的功能,不知道該如何使用而困惑,這種事情不僅限於React以外的框架」。
看到這點我覺得實在是太對了,不禁笑了出來。
→ 因為React,你忘了(或者根本不知道)的事情 – Josh Collinsworth的博客

就个人而言,我认为useEffect的问题是由于对它的定义不清楚所导致的,而不是它的工作原理。重点在于它是什么,而不是它是如何运作的。

Vue的生命周期钩子和watch

在Vue中,它通过生命周期钩子函数和watch的概念将React中的useEffect模糊的责任明确化,并为我们提供了清晰的整理。

如果用Vue来编写上述的React代码,代码将如下所示。

const initialize = () => {};
onMounted(() => {
	initialize();
});

const [show, setShow] = useState(false);
const [param, setParam] = useState({...});
const calculate = () => {};
watch(() => show, () => {
	if (show) {
		calculate();
	}
});

需要清楚明确地知道触发了哪个处理动作。

顺便提一下,我在React中编写了两个自定义钩子的名称分别是useMounted和useWatch,并且我决定在代码中使用它们。

export function useMounted(process: () => void) {
    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(process, []);
}

export function useWatch(process: () => void, deps: DependencyList) {
    // eslint-disable-next-line react-hooks/exhaustive-deps
    useEffect(process, deps);
}
const initialize = useCallback(() => {...}, []);
useMounted(() => {
  initialize();
});

const [show, setShow] = useState(false);
const [param, setParam] = useState({...});
const calculate = useCallback(() => {...}, [param]);

useWatch(() => {
	// showフラグが立った時にcalculate実行
	if (show) {
		calculate();
	}
}, [show])

在定义useMounted和useWatch函数内忽略ESLint警告的原因是明确的,并且在使用这些函数的代码中,可以清楚地知道每个函数在何时被调用。

最近我发现,在react-use这个包中也提供了一些钩子,比如useMount等,看来有其他人也在考虑类似的事情。

状态管理功能易于使用

在Vue2时代,结合Vuex和vuex-module-decorators的组合是相当方便的,但是从Vue3开始,provide/inject被默认引入,更加易于使用。

与上下文进行比较

React的Context和Vue的provide/inject在获取下层组件的值的方法上相似。

const { value } = useContext(myContext);
const { value } = inject('dataStoreKey');

然而,可能React的上下文(Context)并不是为了像官方文档示例中的dark/white那样简单地从上到下传播并批量改变组件外观的情况而设计的,我认为它主要是为了被用作状态管理机制使用时,存在以下缺点。

    • Context内の値が変わると、配下のコンポーネント全てに再描画が走る。

 

    Context内の値を変更するためのsetterの定義方法が、ちょっと変。

后者指的是什么呢?当定义上下文时,需要定义默认值,并且当尝试将setter传递到较低级组件时,需要首先将()⇒{}定义为默认值。

最初是为了设置原始类型的值而准备的氛围地方,现在却强烈感受到了”因为JavaScript中函数也可以被设置为变量…”的想法并把函数类型塞进去的感觉。

export const MyContext = React.createContext({
	a: 0,
	setA: () => {}  // デフォルト値として空Functionを定義
});
export function Root() {
	const [a, setA] = useState(0);
	const myContextValue = {
		a,
		setA  // 実際の値として、stateのsetterを設定
	}
	return (
		<MyContext.Provider value={myContextValue}>
			...
		</MyContext.Provider>
	)
}	

另外,Context的类型定义和定义Context实际处理的值的地方不同,稍显晦涩难懂。
(具体代码解释如下,Context的类型定义在根组件的外部进行,而实际处理的值则在根组件的内部进行定义)

如果使用Vue的话,

    1. 在状态管理中定义值和setter的钩子,

对于Vue的情况 – 状态管理定义
export default function useData() {
const a = ref(0);
const setA = (val) => { a.value = val };
return {
a,
setA,
}
}

并将其传播到子组件。

对于Vue的情况 – 提供给子组件
export default defineComponent({
name: ‘Root’,
setup() {
const data = useData();
provice(‘datastore’, data);

只需要像Vue的常规钩子一样以类似的格式来定义状态管理的概念即可,这样很容易理解。

另外,当需要管理的值变得很多,并且需要按照值的类型进行分割管理时,React的Context使用嵌套的ContextProvider来实现,这种做法有点笨拙。

<AContext.Provideer value={aContextValue}>
	<BContext.Provideer value={bContextValue}>
		<CContext.Provideer value={cContextValue}>
				...

如果是Vue的情况,可以如下并列列举。

export default defineComponent({
	name: 'Root',
	setup() {
		const dataA = useDataA();
		provice('datastoreA', dataA);
		const dataB = useDataB();
		provice('datastoreB', dataB);
		const dataC = useDataC();
		provice('datastoreC', dataC);

与Redux相比较

尽管Context适用于传播简单的值的用途,但Redux被视为集中式、紧密管理状态的方案。
与Vue2中使用的状态管理库Vuex基本结构相似,但存在以下困难。

だいぶ煩雑。初期構築時は毎回マニュアル見ながらじゃないと、基本構成ファイル(reducer、thunk)を作るのが難しい。

同じ値に関する処理が、reducerとthunkの2ファイルに分離しているため処理を追いづらい。

↑aの値がどこから来た値なのか追う際に、reducer、thunkの2ファイルを参照する必要がある。

reducerで管理している値を、共通的に加工する処理は別途カスタムフックを用意して実装する必要がある。

対するVueは、状態管理フックの中にcomputed定義を入れればOK。

Vueの場合
export default function useData() {
const a = ref(0);
const setA = (val) => { a.value = val };
// computedを追加
const customA = computed(() => a * a };
return {
a,
setA,
cusstomA,
}
}

我认为Vue相对于Redux的优点在于,它具有简单的描述方式和在相邻位置编写相似处理逻辑的能力。

与后坐力的比较

Recoil是一个具有与Vue相等甚至更高潜力的功能,原因如下。

    • Vueと同様に状態を細かなカテゴリで定義できる

 

    • Vueのcomputed的なことのできるselectorという機能も用意されている

 

    Suspenseと連動した非同期処理を実装できる

不过,目前来看,以下可能是消极因素吧。

    • Recoil内でフックが使えないので、Contextと併用する場合にブリッジ的な処理を組み込んで、同じ値をRecoilとContextの両方で管理する必要が生じる場合がある。

 

    開発体制の雲行きが怪しい…。

在这种情况下,总结状态管理的话,我认为Vue比React更优秀的地方就是它内置了一个方便使用的状态管理机制。

~~~~~~~~~~~~~~

(Please provide specific content for me to paraphrase in Chinese.)

我认为Vue在以下方面比React更优秀。

我一边写着一边想:“还是Vue好啊~”,但实际上,在我个人开发的项目中,我更倾向于选择React,考虑到一些相关原因,下次我将写一篇关于React相对优越的方面以及在开始开发时的技术选择观点的文章。

广告
将在 10 秒后关闭
bannerAds