| ✍️ Tangxt | ⏳ 2020-07-22 | 🏷️ 技巧 |
文档不会说的内容
v-model 不是双向绑定经典答法:
React 是单向数据流,而 Vue 则是双向绑定的
不过,这个答案是错的,但是你就是可以这样回答! -> 因为使用 React 的人,对 Vue 的了解,肯定是停留在以前的 Vue ,以前的 Vue 确实是双向绑定,但是现在的 Vue 是咩有双向绑定的,全部都是单向的
总之,去一个 React 的公司就告诉他,Vue 是双向绑定的,React 是单向绑定的,反之,去一个 Vue 的公司,就告诉他「Vue 1 是以双向绑定为主的,而 Vue 2 逐渐地就使用单向数据流了,而v-model是语法糖 」 -> 对不同的公司给出的答案是不一样的!
input & #app 的关系就像是子组件与父组件一样

在
#app这个区域看到的变量、表达式啥的,都是#app这个父组件的数据哈! ->input这个子组件内部会触发input事件,并把用户输入的value带上来给父组件,父组件会监听到这个事件,所以$event.target.value的值就是用户输入的value,msg2 = value就是在改父组件的msg2的值,这个msg2一变化,就会响应到:value="msg2"
Vue 记不住光标的位置:

DOM 变化情况:

可以看到,所谓的「双向绑定」其实是单向的……即 用户在页面上改 DOM -> 事件 -> msg2 = 用户输入的值 -> DOM 更新……注意这是跨父子组件的更新!而不是子组件的更新,直接就反映在子组件的 DOM 上了……如果真这样,那才是真正意义上的「双向绑定」!
话说,
$event.target指的是不是那个触发事件的元素,如上边的span元素! ->e.currentTarget始终是监听事件者(xxx.addEventListener()),而e.target是事件的真正发出者(触发事件的那个元素)
从 Vue 0.x 开始,Vue 就用 v-model来实现「双向绑定」。
如:
data: {
user: { name: 'xxx'}
}
<input v-model="user.name">
能实现以下两个绑定:
user.name 的变化自动同步到 input.valueinput.value 的变化自动同步到 user.name但是当 FLUX 单向数据流兴起之后:

Flux 是一种架构思想,专门解决软件的结构问题。它跟 MVC 架构是同一类东西,但是更加简单和清晰。Flux 存在多种实现(至少 15 种),常用 Facebook 的官方实现 -> Flux 会将一个应用分成四个部分。
Vue 的作者重新审视了「双向绑定」,发现「双向绑定」的一些问题之后,就更倾向于「单向绑定」了。v-model 被拆成两部分
data: {
user: { name: 'frank'}
},
<input :value="user.name" @input="user.name = $event">
这样一来,数据的变更权就回到数据拥有者手上了。
用一个 contenteditable 的 bug 来理解单向数据流。
同时为了巩固这一模式,Vue 规定子组件不能修改父组件传给它的 props,一旦发现就会打印出一个警告。
我们以保存用户名来举例说明什么是单向数据流。
可以看出起优点有:
这些都是在双向绑定中很难做到的。
用户改了数据,就会立刻生效 -> 数据是不受控制的!

而单向数据流则可以:
data)清楚地知道数据变化的原因和时机(因为是它自己操作数据的)就像是 React 那样可以阻止用户对表单直接输入那样……
形象点来说:


注意,以前的双向绑定是真得双向绑定啊!没有什么事件在改
data之前杵着,你往input里输入数据,就会立即改内存里的data,中间不带喘气的,而现在的v-model你看起来以为是双向绑定,但其实并不是啊!也是单向数据流呀! -> Vue 现在是通过两个单向绑定(:value="xxx"「父到子传数据」和@input="xxx = $event.target.value"「子到父传数据」)来模拟双向绑定呀!即 Vue 的双向绑定是假的双向绑定,所以这就是为啥我们可以拦截数据的原因!
A input维护自己的一个data,B input维护自己的一个data,data就是组件的状态! -> 我们为input文本框输入的内容也是input这个组件的状态! -> 双向绑定的感觉就像是把input和#app这俩个组件融合到一起了……如<input type="text" v-model="msg1">,你输入内容,就是在改变input的状态,而这个状态情况能实时交给msg1,就像是这样<div>🟡🟡msg1🟡🟡</div>,你改msg1的值,就是在往input#text里输入内容,也就是在为msg1赋值……

看看 0.x 的 Vue 的双向绑定:Handling Forms - vue.js
不好的姿势:

selected是父元素拥有的,如果你直接在儿子里边改父元素的这个selecteddata 的话,那就是在搞双向绑定了! ->props过来的data可不是子组件的!只是父组件给子组件用的data对了,子组件直接改
selected的值,并不会也改了父组件的value值,即便value是引用类型的值也没事儿(疑问???)…… -> 这相当于是:子组件是一个函数,selected是形参,父组件传了个value过来作为子组件的实参,子组件的函数体,selected = 1-> 问题来了,父组件传的value是个{name:'father'}呢? -> 经过我测试,或者说,对 JS 函数的基本认识,引用类型的参数值,可以在函数体内修改同一个引用地址……

如果是引用类型的value:

通知爸爸去 update data:

通过
$event可以拿到emit过来的1,注意这只能传一个值哈!
这种姿势,控制台没有警告,可以看到 vue 更倾向于单向数据流,而不是子元素直接改父元素传入的这个selected值!也就是父元素的那个value值(实参)!
vue1 用户要吃糖:

写法上看,对子组件的操作,数字化,也就是传了一个参数
1 or 2给父组件value = 1 or 2
脱糖:

我们点击
button,冒泡 -> 发布-订阅 ->update:selected事件的callback触发执行!
<div :selected.sync="x"> 等价于 <div :selected="x" @update:selected="x = $event">
例子:
Vue.component('child', {
props: ['selected'],
template: `
<div>
selected: 🟡🟡selected🟡🟡
<hr>
<button @click="$emit('update:selected',1)">1</button>
<button @click="$emit('update:selected',2)">2</button>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
value: 2
},
template: `
<div>
爸爸
<div style="border: 1px solid red;">
<child :selected="value" @update:selected="selected=$event"></child>
</div>
</div>
`
})
.sync 的作用和 v-model 一模一样,就是用双向绑定的语法糖,实现两个单向绑定。好处同上。
tabs轮子写一个 tabs 功能是很好写的,但是造一个 tabs 轮子则是很难造的……

这叫写出一个 Tabs 功能,可不是一个 Tabs 轮子,因为如果人家要用你这个 Tabs 的话,需要拷贝代码,然后一点一点地修改代码,如 selectedTab === 'a'、selectedTab === 'b'……
所以说,这不叫写一个 Tabs 轮子
似乎平时抄人家的代码就是在抄功能呀!
那么什么叫写一个 Tabs 轮子呢? -> 你造一个出来就知道了
template结构,就能实现这样的 Tabs 效果……


图中的
<slot>要闭合一下 -><slot/>,不然会报错,虽然不闭合也可以正常显示内容……
不要用
v-show来控制组件的显示和隐藏 -> 用class吧!
Q:孙子如何得到`selectedTab`的值?
爷爷(tabs)告诉爸爸(tabs-navs),爸爸告诉儿子 (tabs-navs-item)
可是爷爷如何告诉爸爸呢?
爸爸是slot,我们要拿到插入进来的tabs-navs
使用 mounted 钩子(created钩子是拿不到的) -> this.$children
由于,爸爸有两个,即tabs-navs & tabs-panes
所以我们需要区分它们俩 -> 定义这俩组件的时候,请添加一个name选项(这是name选项的意义之一,还有一个意义是使用 vue-devtools 来方便debugger)
遍历this.$children -> vm.$options.name可获取tabs-navs or tabs-panes
如果是tabs-navs组件实例,那么就vm.selectedTab = this.selectedTab
我们vm.selectedTab,就是在给tabs-navs实例定义了一个叫selectedTab的prop -> 所以tabs-navs的选项需要有个props:['selectedTab']
同理,爸爸 -> 儿子,也是爷爷 -> 爸爸这样一个过程
整个过程来看,孙子如何得到selectedTab的值的呢?
口口相传:爷爷 -> 爸爸 -> 儿子
测试,结果咩有正常运行
因为mounted的顺序,是从里到外的,即儿子先 append 在爸爸,然后再是爸爸 append 爷爷……最后是爷爷 append 到 app,即 mounted 到页面上!
而这意味着,我们本来以为mounted是从高处往下走的,即selectedTab这个数据从高处流向低处,但其实mounted是从低处往上走的!即低处的mounted是无法通过this.selectedTab拿到值的,因为此时并无水从高处流下来呀!

虽然儿子等咩有 mounted 到页面上,但它们 mounted 上一级也是可以的,而它们 mounted 完之后,就处于组件的运行状态了!
created的顺序是从上往下流的!
所以这个思路得变了……
思路:
selectedTab的值,所以爸爸的selectedTab就不能写在props里边了,而是写在data里边了!tabs上的selectedTab为1 or 2,会不会有相应的变化!不行呀!
儿子点击tab-navs-item(有@click) -> $emit的自定义事件,如xxx,是不会冒泡的…… -> 所以你得为儿子、爸爸、爷爷都得$on一个xxx事件 -> 到爷爷后,你得updated一下,因为selectedTab的值变化了,你得更新一下爷爷的selected值!不然,不会自动更新组件的状态!


value:'tab1'的初始化data代码:https://jsbin.com/zewuvir/edit?js,output
tabs-navs-item的值可以是标签等,没有任何约束,想写啥就写啥,而 element-ui 的用了一个label,这样就只能规定是字符串值了!还有为啥要用name呢(这个解释得不清楚……)?不按书写tabs-navs-item的顺序来排列tab1、tab2这样嘞? -> 因为希望点击tab1时是跳转到其它页面……Vue 的另一个大特点就是「渐进式」,意思就是你可以渐渐地用 Vue。而 React 几乎做不到这一点。
new Vue({})不能挂载在body上! -> Vue 的潜规则! -> 不然body元素很有可能被组件替代之,这样页面就没有body元素了!对于小公司来说,使用 Vue 可以渐进式的改造网站 -> 假如页面有 10 个模块,都是用 jQuery 搞的,我们可以从中选择一个模块用 Vue 来搞 -> 逐渐地,最后可以让 10 个模块都变为用 Vue 来搞!

SEO:把那些标题等,用于在服务器端渲染,而那些模块则用 Vue 就好了,而 React 则希望 body 旗下的第一个元素就是 React App

<div ref=div5></div>,来让我们这样获取 DOM 元素: vm.$refs.div5 -> 抛弃了 jQuery 获取元素的 API,虽然如此,我们还是可以用 jQuery 对 DOM 进行 CRUD 的! -> 而 React 则是直接全家桶……admin这样的页面就用单页了 -> 整站可以 50%用单页,也可以 25%用单页……… -> 这就是渐进式,而 React 则很少有人这样搞,因为 React 做出来的页面都是 JS 渲染出来的,没有 SEO……补充:


computed、watch、methods的区别watch 的值变化的时候执行一个函数getMessage() 出现在视图里的时候,或视图更新的时候调用 getMessagemessage 出现在视图里了或视图更新了data 承载,本身返回值没有用其它看文档
watch有 bug,那么 vue 肯定会去修复的,所以最终的结果是:Vue 提供的 API 没有 bugwatch?
data:{n:1}的n是不会触发事件的!n变了呢? -> 所以就只能通过一些特殊的手段,如watch,watch就是对数据来一个getter、setter的拦截 -> watch一个data,如n,那么就会知道n是怎么变化的……而且这没有什么注意事项n>10就报出一个警告,如this.message = '警告'watch和computed的区别?
wathc做,也可以用 computed 做 -> 至于怎么做,要通过你的经验来决定……message(){ return this.n > 10? '警告':''}computed,又有watch呢?
computed?
message都自己去赋值就好了,即在 data 里定义一个message,然后watch:{n(value){if(value>0){this.message = '警告'}}}computed实际上都是用了与watch一样的语法来做的,即getter&setter -> 当我们这样定义了一个message,如computed:{message(){}} -> 在模板里🟡🟡message🟡🟡,实际上这是🟡🟡get message(){}🟡🟡,只不过 ES6 给了我们不需要加括号就可以调用message这个函数了,总之:🟡🟡message🟡🟡里的message就是,computed里的message方法的封装!watch能做的,computed都能做!反之,亦然…… -> 那么什么时候用什么呢? -> 看你的喜好!methods能做的,watch和computed都能做!反之,亦然……
🟡🟡getMessage()🟡🟡 -> methods:{getMessage(){ return this.n > 10 ? '警告':''}}methods姿势看起来要弱一点,因为需要加个()调用!watch、computed、methods
🟡🟡getMessage()🟡🟡需要跟一个括号!,而computed不用加括号直接🟡🟡message🟡🟡,还有watch则是需要在data里边搞个message来承载一个值……而computed、methods则不需要!computed -> message调用的时机:
🟡🟡message🟡🟡,不然,就不会调用message🟡🟡message🟡🟡渲染的时候,发现它依赖的元素没有变化,那么message就不会重复调用,说白了,假如template是这样的:🟡🟡y🟡🟡 🟡🟡message🟡🟡,y变了,message是不会去执行的(做了个缓存),假如message依赖的this.n变了,🟡🟡message🟡🟡才有可能变化 -> 而🟡🟡getMessage()🟡🟡,只要template发生了渲染,就会调用getMessage方法! -> 所以:computed就是只会在依赖变化时才会调用的methods!,watch只在依赖变化时才会执行的函数,跟methods的区别是,必须要有一个message来承载一个值才行,毕竟我return的值是没有用的!,简单来说,watch只执行一个动作,computed则是执行一个动作,并且把这个动作赋值给message,而methods也是执行一个动作,可见,wathch和methods看起来是很像的,而它们俩的区别在于触发的时机不同,watch只在n(){}这个n变化时触发,而methods只在我们调用getMessage方法时触发,n是否变化我不管,而computed则是综合二者,是一个优化最多的功能,可认为是watch+methods,如computed:{message(){ return this.n}},必须要message在页面上被渲染,同时n要变化computed = watch + methodswatch跟踪data旗下属性的变化,methods则是跟踪template是否有某个🟡🟡xxx🟡🟡渲染过!而computed则是既跟踪依赖的属性是否有变化,又跟踪你有没有在template里边写了这个计算属性,如🟡🟡message🟡🟡computed、watch、methods三者的区别试着写一个 Tabs 组件,API 在以下两种风格中二选一:
<!-- 风格一: -->
<template>
<el-tabs v-model="activeName" @tab-click="handleClick">
<el-tab-pane label="用户管理" name="first">用户管理</el-tab-pane>
<el-tab-pane label="配置管理" name="second">配置管理</el-tab-pane>
<el-tab-pane label="角色管理" name="third">角色管理</el-tab-pane>
<el-tab-pane label="定时任务补偿" name="fourth">定时任务补偿</el-tab-pane>
</el-tabs>
</template>
<script>
export default {
data() {
return {
activeName: 'second'
};
},
methods: {
handleClick(tab, event) {
console.log(tab, event);
}
}
};
</script>
// 风格二:
<tabs class="tabs" :selectedTab.sync="x">
<tabs-navs>
<tabs-navs-item name="ChoiceQuestion">
选择题
</tabs-navs-item>
<tabs-navs-item name="ClozeQuestion">
填空题
</tabs-navs-item>
</tabs-navs>
<tabs-panes class="tabs-panes">
<tabs-panes-item name="ChoiceQuestion">
1
</tabs-panes-item>
<tabs-panes-item name="ClozeQuestion">
2
</tabs-panes-item>
</tabs-panes>
</tabs>
watch、computed、methods三者的区别 -> 自己写 demo 去测试得结论,而不是看源码!毕竟源码对于新手而言,无异于是在看天书!不传参数:
<button v-on:click="click">click me</button>
click(event) {
console.log(typeof event); // object MouseEvent
}
给括号不传参数:
<button v-on:click="click()">click me</button>
click(event) {
console.log(typeof event); // undefined
}
给括号也传参数:
<button v-on:click="click($event, 233)">click me</button>
click(event, val) {
console.log(typeof event); // object
}
所以:
event 对象将被自动当作实参传入;$event 变量显式传入 event 对象event是个关键字!可假如我们这样呢:

让event失效的姿势是,把event作为形参,然后不给$event实参!
总之,$event就是MouseEvent,如果你的handler没有用event作为形参,你也咩有传入$event实参,那么获取MouseEvent的姿势就是event
对了,当子组件$emit('enlarge-text', 0.1),父组件v-on:enlarge-text="postFontSize += $event"时,通过 $event 可以访问到被抛出的这个0.1值 -> 这其实类似于我们原生点击一个button时,默认就会有一个e作为第一个参数传给callback一样,同理0.1这个值也是第一个参数!
➹:深入理解 e.target 与 e.currentTarget - 掘金