Appearance
常见双向绑定需求
本节目标:理解“常见双向绑定需求”的核心思路,并能把它用于实际开发或面试表达。 单向数据流**** 所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。
另外,每次父组件更新后,所有的子组件中的 props 都会被更新到最新值,这意味着你不应该在子组件中去更改一个 prop。若你这么做了,Vue 会在控制台上向你抛出警告: const props = defineProps(['foo']) ❌ 警告!prop 是只读的! props.foo = 'bar'
导致你想要更改一个 prop 的需求通常来源于以下两种场景: 一、prop 被用于传入初始值;而子组件想在之后将其作为一个局部数据属性。在这种情况下,最好是新定义一个局部数据属性,从 props 上获取初始值即可: const props = defineProps(['initialCounter'])
// 计数器只是将 props.initialCounter 作为初始值 // 像下面这样做就使 prop 和后续更新无关了 const counter = ref(props.initialCounter) 这种情况很少有用,因为使用Setup写组件时,父组件的props实际上是会变的,我们要在props变化的时候,让这个ref的值也变化,而这个父组件的props往往和是通过网络请求异步更新的,而setup函数只是相当于created,在更新周期中,不会触发;所以往往ref拿到的初始值不是网络请求返回的真正想要的初始值;难道在onUpdate钩子中再设置一次吗?还是下面这个第二种方式;
二、需要对传入的 prop 值做进一步的转换。在这种情况中,最好是基于该 prop 值定义一个计算属性: const props = defineProps(['size']) // 该 prop 变更时计算属性也会自动更新 const normalizedSize = computed(() => props.size.trim().toLowerCase()) 这又会产生问题,计算属性是只读的,所以,如果这个normalizedSize在组件内会被改变的话,它必须是一个有get和set方法的计算属性;而在这个set方法中,我们要改变get方法中的依赖的项,也就是props,而这只能通过emit一个事件出进行,保持单向数据流;
更改对象 / 数组类型的 props**** 当对象或数组作为 props 被传入时,虽然子组件无法更改 props 绑定,但仍然可以更改对象或数组内部的值。这是因为 JavaScript 的对象和数组是按引用传递,而对 Vue 来说,禁止这样的改动,虽然可能生效,但有很大的性能损耗,比较得不偿失。
这种更改的主要缺陷是它允许了子组件以某种不明显的方式影响父组件的状态,可能会使数据流在将来变得更难以理解。在最佳实践中,你应该尽可能避免这样的更改,除非父子组件在设计上本来就需要紧密耦合。在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。
