参数:html
{string | Function} expOrFn
{Function | Object} callback
{Object} [options]
{boolean} deep
vue
{boolean} immediate
{Function} unwatch
观察 Vue 实例变化的一个表达式或计算属性函数。回调函数获得的参数为新值和旧值。表达式只接受监督的键路径。对于更复杂的表达式,用一个函数取代。webpack
例如咱们在某种获取搜索列表的场景就至关适合使用web
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <input type="text" v-model="searchWord"> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { searchWord: '' }, created() { // 获取搜索列表 this.getSearchList() }, watch: { // 监听数据变化获取搜索列表 searchWord(oldVal, newVal) { this.getSearchList(oldVal, newVal) } }, methods: { getSearchList(oldVal, newVal) { console.log('得到值: ', oldVal, newVal) console.log('getSearchList') } } }) </script> </body> </html>
咱们能够优化成正则表达式
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> </head> <body> <div id="app"> <input type="text" v-model="searchWord"> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> var app = new Vue({ el: '#app', data: { searchWord: '' }, watch: { // 监听数据变化获取搜索列表 searchWord: { handler: 'getSearchList', immediate: true, deep: true } }, methods: { getSearchList(oldVal, newVal) { console.log('得到值: ', oldVal, newVal) console.log('getSearchList') } } }) </script> </body> </html>
handler
声明调用方法名immediate
表示组件建立的时候当即执行一次(效果等同create生命周期内调用)deep
为了发现对象内部值的变化,数组不须要添加加载过程vue-router
父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
更新过程express
父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
销毁过程npm
父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
咱们习惯于用Vuex去解决状态的共享问题,可是在小项目中使用就会有增大代码体积和将代码复杂化的烦恼,因此在后来的版本中Vue新增api
Vue.observable( object )
让一个对象可响应,Vue 内部会用它来处理 data 函数返回的对象。数组
返回的对象能够直接用于渲染函数
和 计算属性
内,而且会在发生改变时触发相应的更新。也能够做为最小化的跨组件状态存储器,用于简单的场景:
const state = Vue.observable({ count: 0 }) const Demo = { render(h) { return h('button', { on: { click: () => { state.count++ }} }, `count is: ${state.count}`) } }
咱们都知道Vue2.x版本是利用Object.definedProperty
对数据进行劫持达到视图响应变化的效果,可是大多数时候咱们不须要变更的数据相似列表页基本不存在这个需求,咱们就能够利用object.freeze
将其 "冻结"禁止修改.
便可以减小劫持数据的性能损耗,但不影响其从新赋值,例如
var app = new Vue({ el: '#app', data: { list: [] }, async created() { const list = await this.getData() this.list = Object.freeze(list) }, methods: { handleChange() { this.list[0].id = 1 // 无效 this.list = [] // 能够修改 } } })
若是有学过React的话应该就能发现一些相同的烦恼,在默写很简单的组件若是都必须实例化一遍是至关烦恼的操做,因此在一些简单而且不须要任何生命周期处理的组件咱们能够将状态做为传参这么写
// List组件 <template functional> <ul> <li v-for="item in props.list" @click="props.handleChange">{{item}}</li> </ul> </template> // 父组件调用方式 <div> <List :list="list" :handle-change="handleChange" /> </div>
在某些须要监听组件生命周期的场景中可能大部分人都是经过传递方法而后在组件生命周期内调用,其实还有一种方法能够直接在父组件就进行调用方式---hook
<!DOCTYPE html> <html lang="en"> <body> <div id="app"> <child @mounted="doSomethings">child</child> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child', { mounted() { this.$emit("mounted"); }, render(createElement) { return createElement('p', this.$slots.default) } }) var app = new Vue({ el: '#app', data: { status: 0 }, methods: { doSomethings() { console.log('doSomethings') } } }) </script> </body> </html>
如下面的一组状态判断按钮为例,咱们很容易就下意识地在模板内写下这种代码
<button v-if="status === 1" class="btn1" :class="status === 1" @click="">未开始</button> <button v-if="status === 2" class="btn2" :class="status === 2" @click="">进行中</button> <button v-if="status === 3" class="btn3" :class="status === 3" @click="">可领取</button> <button v-if="status === 4" class="btn4" :class="status === 4" @click="">已领取</button>
可是若是咱们利用渲染函数能够将上面的代码抽取成优雅的使用组件
<!DOCTYPE html> <html lang="en"> <body> <div id="app"> <child :status="status"></child> </div> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script> Vue.component('child', { props: { status: { type: Number, required: true } }, render(createElement) { const innerHTML = ['未开始', '进行中', '可领取', '已领取'][this.status] return createElement('button', { class: { active: this.status }, attrs: { id: 'btn' }, domProps: { innerHTML }, on: { click: () => console.log(this.status) } }) } }) var app = new Vue({ el: '#app', data: { status: 0 } }) </script> </body> </html>
咱们将全部的逻辑封装进渲染函数内,外部只须要传递一个状态参数便可改变
<child :status="status"></child>
假设咱们有一个详情路由,咱们从 /detail/a 跳转到 /detail/b ,可是由于vue-router
认为这是同一个组件,因此不会从新执行 created
内的代码,咱们通常的解决办法是监听 $router 的变化执行代码
created() { this.init() }, watch: { '$route'() { this.init() } }, methods: { init() {} }
可是咱们其实能够在路由组件设置惟一的 key
解决缓存问题
<router-view :key="$route.fullpath"></router-view>
注意这个方法会增长额外消耗
原始方法有几种:
从新加载页面,弊端也很明显,从新运行全部流程,全部数据不做特殊处理会丢失,有可能被重定向到其余路由
有些浏览器会原路由刷新,有些浏览器会返回首页,具体缘由没去探究
页面不会进行刷新,可是能够结合上面的路由缓存作处理
这对选项须要一块儿使用,以容许一个祖先组件向其全部子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
<router-view v-if="isRouterAlive" /> // 父级组件提供 'foo' var Provider = { provide() { return { reload: this.reload } }, data() { return { isRouterAlive: true } }, // ... } // 子组件注入 'foo' var Child = { inject: ['reload'], created () { this.reload() } // ... }
咱们在父子组件进行 props 传递时,一般须要声明一大堆属性
//父组件 <BaseInput :value="value" label="密码" placeholder="请填写密码" @input="handleInput" @focus="handleFocus> </BaseInput> //子组件 <template> <label> {{ label }} <input :value="value" :placeholder="placeholder" @focus=$emit('focus', $event)" @input="$emit('input', $event.target.value)" > </label> </template>
默认状况下父做用域的不被认做 props 的特性绑定 (attribute bindings) 将会“回退”且做为普通的 HTML 特性应用在子组件的根元素上。当撰写包裹一个目标元素或另外一个组件的组件时,这可能不会老是符合预期行为。经过设置 inheritAttrs
到 false
,这些默认行为将会被去掉。而经过 (一样是 2.4 新增的) 实例属性 $attrs
可让这些特性生效,且能够经过 v-bind
显性的绑定到非根元素上。
注意:这个选项不影响 class
和 style
绑定。
咱们能够利用如下方式 $attrs
将原生属性直接传递给子组件
<input v-bind="$attrs" :value="value" @focus=$emit('focus', $event)" @input="$emit('input', $event.target.value)" >
这是Vue在2.4.0
新增的属性,包含了父做用域中不做为 prop 被识别 (且获取) 的特性绑定 (class
和 style
除外)。当一个组件没有声明任何 prop 时,这里会包含全部父做用域的绑定 (class
和 style
除外),而且能够经过 v-bind="$attrs"
传入内部组件——在建立高级别的组件时很是有用。
<input :value="value" v-bind="$attrs" v-on="listeners" > ---------------------------------------------------------------------- computed: { listeners() { return { ...this.$listeners, input: event => this.$emit('input', event.target.value) } } }
包含了父做用域中的 (不含 .native
修饰器的) v-on
事件监听器。它能够经过 v-on="$listeners"
传入内部组件——在建立更高层次的组件时很是有用。
当项目规模和组件化达到必定程度的时候,咱们最常开始的准备工做就是引入组件模块 -> 声明组件
若是咱们利用webpack 解析 require()
调用,而后提取出以下一些信息:
Directory: ./template Regular expression: /^.*\.ejs$/
生成一个 context module(上下文模块)。它包含目录下的全部模块的引用,是经过一个 request 解析出来的正则表达式,去匹配目录下全部符合的模块,而后都 require 进来。此 context module 包含一个 map 对象,会把 request 中全部模块翻译成对应的模块 id。
例如
{ "./table.ejs": 42, "./table-row.ejs": 43, "./directory/folder.ejs": 44 }
此 context module 还包含一些访问这个 map 对象的 runtime 逻辑。
这意味着 webpack 可以支持动态地 require,但会致使全部可能用到的模块都包含在 bundle 中。
咱们能够经过 require.context()
函数来建立本身的 context。
require.context(directory, useSubdirectories = false, regExp = /^\.\//);
能够给这个函数传入三个参数:一个要搜索的目录,一个标记表示是否还搜索其子目录, 以及一个匹配文件的正则表达式。
webpack 会在构建中解析代码中的 require.context()
。
/* 搜索当前目录及其全部子目录下vue后缀结尾文件,并返回webpack上下文函数以下 webpackContext(req) { var id = webpackContextResolve(req); return __webpack_require__(id); } */ const requireComponent = require.context('./', true, /\.vue$/) // 获取requireComponent的索引值获得模块路径数组,例如 ['./A.vue', './B.vue', ...] const components = requireComponent.keys() // 遍历注册模块组件 components.forEach(filePath => { // 组件路径 const config = requireComponent(filePath) // 提取文件名, filePath: ./A.vue -> fileName: B const fileName = validateFileName(filePath) const name = fileName.toLowerCase() === 'index' ? capitalizeFirstLetter(config.default.name) // 组件自定义name为注册名,例如 name: helloWorld -> HelloWorld : fileName // 不然用文件名 // 调用Vue的API注册组件,第二个参数就是组件的配置,相似 {data: ƒ, computed: {…}, activated: ƒ, deactivated: ƒ, methods: {…}, …} Vue.component(name, config.default || config) }) // 拼凑大写开头单词 function capitalizeFirstLetter (str) { return str.charAt(0).toUpperCase() + str.slice(1) } // 提取文件名 function validateFileName (str) { return ( // 匹配任何非空白字符的vue文件并提取匹配值 /^\S+\.vue/.test(str) && str.replace(/^\S+\/(\w+)\.vue$/, (rs, $1) => $1) ) }