@eGust 试了一下,有意思。
const [counter, setCounter] = useState({ value: 1, step: 1 })
useEffect(() => {
console.log('A', { ...counter })
setCounter((old) => {
console.log('AA', { ...old })
return { value: old.value + old.step, step: old.step }
})
}, [counter.step])
useEffect(() => {
console.log('B', { ...counter })
setCounter((old) => {
console.log('BB', { ...old })
return { value: old.value + old.step, step: old.step }
})
}, [counter.step])
step 1->2 时,外层 A B 处拿到的两个 counter 是一样的,如你所说。
但是,setCounter内层 AA BB 处拿到的两个 old 是不一样的,反而和 vue 类似,拿到的一个是没加过 step 的,一个是加过的。
所以 react 最终页面效果还是value加了2,和vue的最终效果一样。
看来一个渲染批次之内的多个 setCount 传入多个 old => new 这样的 reducer 函数的时候,是 lazy evaluation,把两个 setCount 压到最后顺序执行了。这个最终效果和你说的有偏差,react 的这种写法,一样有你说的调用顺序的“微妙”问题。也就是说,那两个
setCount,和 vue 的两个 watch 一样,都有先后次序的问题。
想想也应该是这样啊,用户写了对同一个内存对象的两次累加修改,在单线程模式下,最后的本质肯定还是一先一后执行的啊,最终肯定得加两次啊。哪怕并发模式,将来有了
cocurrent,正确的逻辑也得是加锁,最终还是要加两次呀。
只要加两次,就肯定无法避免先后次序问题。咱们这个例子只是加一,顺序无所谓。碰上顺序有关系的,反而是 cocurrent 比较麻烦,vue 和 non-cocurrent react 还好,不管怎么 lazy evalute,最终应该还是按照代码里的真实顺序来调用的,次序虽然不一定直观,但应该是稳定的。
【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: 标 题: Re: 对比 vue,reactjs 就是个笑话!
: 发信站: 水木社区 (Wed Jun 30 21:52:25 2021), 站内
:
: 我咋觉得react也一样呢?复刻一下这个例子:
:
: const [{ value, step }, setCounter] = useState(counter);
:
: useEffect(() => {
: console.debug("watcher A", { ...counter });
: setCounter(old => ({ value: old.value + old.step, step: old.step })) // 1
: }, [step]);
:
: useEffect(() => {
: console.debug("watcher B", { ...counter });
: setCounter(old => ({ value: old.value + old.step, step: old.step })) // 2
: }, [step]);
:
: 跑的时候 1 和 2 两个 setCounter 也是顺序运行的,它们各自取到的 old.value 也不一样吧?
:
: 我个人理解,useState useEffect 本质就是为了造成纯组件函数的“假象”,把生存周期是跨越多次渲染而一直存在的 state 变量、effect 函数,都存到了用户看不见的闭包里,因为 react 函数组件只有一层 render 函数,正常来看的话没有造闭包的地儿:
:
: ...; // state 和 effect 真实创建于框架代码里,也就是组件 render 函数体之外
:
: function Component () {
: ...; // render 通过 useState useEffect 取出作用域外的闭包变量
: }
:
: 而 vue 是把这种变量显式地放在 setup 函数形成的闭包里。
:
: setup() {
: ...; // 构造跨多次渲染的闭包变量
: return () => {
: // render 直接通过原生js作用域机制获取外面的闭包变量
: }
: }
:
: 所以本质都是闭包变量,只是闭包的位置不同而已吧?
:
: 除非说 react 内部在上述同一批次内多次调用 setCount 的时候,故意都是传入初始的
: count,而不是前面 setCount 修改过的 count。。。如果真是这样,我觉得反而不符合用户意图啊?
:
: 稍后有空试一下。
:
:
:
:
: 【 在 eGust (十年) 的大作中提到: 】
: : 标 题: Re: 对比 vue,reactjs 就是个笑话!
: : 发信站: 水木社区 (Wed Jun 30 16:51:15 2021), 站内
: :
: : 重点并不在 watch 上,declarative ui 是 model -> view 的映射。理论上只要给定了
: 一个确定的状态,state 也好 data 也好,都能生成一个确定的 ui。
: :
: : react 里面 newState = reduce(oldState, action),这是一个非常确定的状态。举一个
: counter.value += Number(counter.step);
: }, [step]);
:
:
: 【 在 eGust (十年) 的大作中提到: 】
: : 标 题: Re: 对比 vue,reactjs 就是个笑话!
: : 发信站: 水木社区 (Wed Jun 30 16:51:15 2021), 站内
: :
: : 重点并不在 watch 上,declarative ui 是 model -> view 的映射。理论上只要给定了一个确定的状态,state 也好 data 也好,都能生成一个确定的 ui。
: :
: : react 里面 newState = reduce(oldState, action),这是一个非常确定的状态。举一个不太恰当的例子:
: : import { reactive, watch, toRef } from "vue";
: :
: : export const counter = reactive({
: : value: 1,
: : step: 1
: : });
: :
: : const step = toRef(counter, "step");
: :
: : watch(step, () => {
: : console.debug("watcher A", { ...counter });
: : counter.value += Number(counter.step);
: : });
: :
: : watch(step, () => {
: : console.debug("watcher B", { ...counter });
: : counter.value += Number(counter.step);
: : });
: :
: : 如果 step 的值改变了:1 -> 2,那么你会看到的输出是
: : watcher A { value: 1, step: 2 }
: : watcher B { value: 3, step: 2 }
: :
: : 如果把这个实现换成 react 的话,由于 value 是从
: : const [{ value, step }, setCounter] = useState(counter);
: :
: : 里取出来的,所以不管有几个 useEffect,调用了几次 setCounter,在所有的 effect 函数里面,value 的值都会是 1。
: :
: : 这个例子本身很不好,实际中的情况是,多个 watchers 监听各自的 props/computed,但它们都会根据变化来和 data 本身的值来更新 data,于是就产生了不确定性。但是一时间不太容易构造一个合适的例子,临时只能想出这个示意一下,不知道解释明白了没……
: :
: : 【 在 beep (菜M.喵星耗子) 的大作中提到: 】
: : : 下面这一段,watcher的复杂性,和useEffect有何不同呢?useEffect如何解决“触发顺序”之类的问题呢?
: :
: :
: : --
: :
: : ※ 修改:·eGust 于 Jun 30 17:00:22 2021 修改本文·[FROM: 101.98.83.*]
: : ※ 来源:·水木社区 mysmth.net·[FROM: 101.98.83.*]
:
:
: --
:
: ※ 修改:·beep 于 Jun 30 22:18:46 2021 修改本文·[FROM: 123.120.168.*]
: ※ 来源:·水木社区 mysmth.net·[FROM: 123.120.168.*]
--
修改:eGust FROM 101.98.83.*
FROM 123.120.168.*