应该是看懂了。说说我的理解哈。
你最后提到的useReduce应该和咱们的话题没有太多直接关系,不涉及react和vue的能力对比问题。我理解useReduce就是把原本分散在各处的setState(v=>foo(v))逻辑都起了个名儿,集中在了一起,方便管理。
关键问题在于对e2这种逻辑的表达能力:
: // e2
: useEffect(async () => {
: const newD = await remoteCall(d, p);
: setD((oldD) => foo(newD, d, oldD)); // 这里我改成了类似你useReduce的通用写法,foo逻辑任由用户指定,爱怎么用这三个d就怎么用,这个理解没错把?
: }, [c]);
: }
: 如果想用 vue 来实现 e2 的话,就会变的非常复杂。
上面三个d,其中d是c发生变化触发effect时刻的d值,newD是await返回的结果,oldD是await返回时刻的d值
用vue的话,也不复杂:
watch([C,D], ([c2,d2], [c1,d1]) => { // 同时 watch c 和 d
if (d1!=d2) return // watch D 只是为了在c变化时拿到d值,所以忽略d的变化
if (c1!=c2) {
const newD = await remoteCall(d2, p) // d2就是上述d的等价
D.value = foo(newD, d2, D.value) // d2等价于d,D.value等价于oldD
}
})
也不复杂,这里的foo函数和上面给react用的foo函数完全一样,可测性也没有差别。
而且如果这个逻辑用的多的话,自己包个composable就变成通用api了,就类似于react官方没提供获取useEffect时的previous值的方法,所以社区自己用useRef写
了usePrevious一样。
所以我还是不觉得这能说明纯函数风格的表达能力有什么本质性的优势。只是因为官方api设计风格不同,所以有的场景vue方便,react需要自己包,另外一些场景react方便,vue需要自己包。
不过我大概能get到你的意思,只是这个具体例子的确不能说明问题。核心问题在于,react把所有的操作状态变化的部分都集中到内部管理,这样在复杂的竞态条件下就可以有一致稳定的的策略来解决,保证结果至少时稳定的而不是随机的。这带来的优势主要时将来并发模式下实现较为简单,但同时,你提到的多个effect在异步条件下也有可能带来隐藏很深的竞态,这里react也有优势。
对比之下,vue可能(还没实验不确定)会如你所说,computed ref 这些东西watch起来时序就可能不稳定。而且基本没可能支持并发模式。
但话说回来,react另一方面又在内部搞fiber,把同一批更新拆散到不同的渲染周期来做,这又反过来带来了时序上的复杂性,可能或多或少会抵消这个优势。
【 在 eGust (十年) 的大作中提到: 】
: 标 题: Re: 对比 vue,reactjs 就是个笑话!
: 发信站: 水木社区 (Thu Jul 1 17:34:33 2021), 站内
:
: 我再尝试解释最后一遍,你要是再看不懂我也真没辙了,vue 的实例代码在这里:
:
https://epuxm.csb.app/: setup(props) {
: const propP = toRef(props, "p");
: const dataD = ref(1);
: const computedC = computed(() => Number(propP.value));
:
: watch(propP, (newP, oldP) => {
: console.debug("w1", { D: dataD.value, newP, oldP });
: dataD.value += Number(newP);
: });
:
: watch(computedC, async (newC, oldC) => {
: console.debug("w2", { D: dataD.value, newC, oldC });
: dataD.value = await remoteCall(dataD.value, newC);
: });
:
: return { D: dataD };
: },
:
: w2 是 async 操作,如果你想写成 useEffect 的形式的话
: setD(async (upToDateD) => {
: const newD = await remoteCall(upToDateD, p);
: return newD;
: });
: 并不是一个合法的操作。
:
: 同理在 react 里面
: const Foo = ({ p }) => {
: const [d, setD] = useState(...);
: const c = useMemo(() => bar(p), [p]);
:
: // e1
: useEffect(() => {
: const newD = f(d, p);
: setD(newD);
: }, [p])
:
: // e2
: useEffect(async () => {
: const newD = await remoteCall(d, p);
: setD(newD);
: }, [c]);
: }
: 如果想用 vue 来实现 e2 的话,就会变的非常复杂。但 e2 的操作可能会产生 bug,更合理的写法是
: const [d, dispatch] = useReducer(reducer, ...)
:
: useEffect(() => {
: const newD = ...;
: dispatch({ type: 'e1', payload: { newD } })
: }, ...);
:
: useEffect(async () => {
: const newD = await ...;
: dispatch({ type: 'e2', payload: { newD, oldD: d } })
: }, ...);
:
: 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : 再贴一下我之前实验的结果:
: : watch(aRef, (v) => aRef.value = v+1) // 触发时候aRef的值固定在了v参数中
: : useEffect(() => setA(a+1)) // 触发时候a的值通过闭包被固定
: : ...................
:
: --
:
: ※ 来源:·水木社区 mysmth.net·[FROM: 122.57.163.*]
--
FROM 123.120.180.*