- created :父組件早于子組件
- mounted :子組件早于父組件
在哪個(gè)組件發(fā)布其實(shí)就是再哪個(gè)組件監(jiān)聽抡驼,只是書寫的時(shí)候書寫到了父組件
//子組件
<h1 @click="$emit('foo',msg)">{{ msg }}</h1>
//父組件
<HelloWorld msg="hello" @foo='onFoo' />
onFoo(msg){
console.log(msg);
}
觀察者模式
class Bus{
constructor(){
this.callbacks={}
}
$on(name,fn){
this.callbacks[name]=this.callbacks[name]||[];
this.callbacks[name].push(fn);
}
$emit(name,args){
if (this.callbacks[name]) {
this.callbacks[name].forEach(cb=>cb(args));
}
}
}
Vue.prototype.$bus=new Bus();
//如果自己實(shí)現(xiàn),則可以new Bus();
Vue.prototype.$bus=new Vue();//main.js中
this.$bus.on('foo',msg);
this.$bus.$emit('foo');
實(shí)踐中可以使用Vue替換Bus,因?yàn)閂ue實(shí)現(xiàn)了相關(guān)功能
祖先和后代之間傳遞褪尝,由于嵌套層數(shù)過多,props不切實(shí)際,provide inject使用場景就在此處房蝉;provide inject主要為高階插件/組件庫使用颊郎,一般不推薦直接使用在代碼里面;provide inject不是響應(yīng)式的他炊,但是如果傳遞的是引用類型則修改內(nèi)部是完全可以的争剿,但是比如說只是傳入的字符串這時(shí)候修改會報(bào)錯(cuò)的官方建議盡量傳入不修改的值
//APP.vue
<template>
<div id="app">
<img src="./assets/logo.png">
<HelloWorld msg="hello" @foo='onFoo' />
<HelloWorld msg="world" @foo='onFoo' />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: 'App',
// provide:{
// haha:'haha'
// },
//provide可以傳遞this已艰,但是不能上面直接寫,指向會有問題蚕苇,如下寫法
provide(){
return{
hehe:this,
tua:this.tua
}
},
data(){
return{
tua:'tua'
}
},
components:{
HelloWorld
},
methods:{
onFoo(msg){
console.log(msg);
}
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
//HelloWorld.vue
<template>
<div class="hello" @click="$parent.$emit('haha',msg)">
<h1 @click="$emit('foo',msg)">{{ msg }}</h1>
<h2>{{tua}}</h2>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
props:{
msg:String
},
inject:['tua'],
created(){
//父組件監(jiān)聽哩掺,父組件發(fā)送,此時(shí)是兩個(gè)HelloWorld組件
this.$parent.$on('haha',(info)=>{
console.log(info);
})
}
}
</script>
<style scoped></style>
模擬實(shí)現(xiàn)element-ui的form組件并實(shí)現(xiàn)校驗(yàn)等功能
注意:自定義組件要實(shí)現(xiàn)v-model必須實(shí)現(xiàn):value @input涩笤,因?yàn)関-model其實(shí)就是這兩者的語法糖
//index.vue
<template>
<div class="hello">
<KForm :model="model" :rules="rules" ref="loginForm">
<KFormItem label="用戶名" prop="username">
<KInput v-model="model.username"></KInput>
</KFormItem>
<!-- prop之所以傳遞嚼吞,是因?yàn)樵谛r?yàn)或者其他場景下需要通過this去獲取實(shí)例,但是此時(shí)KFormItem有兩個(gè)需要通過key區(qū)分 -->
<KFormItem label="密碼" prop="password">
<KInput v-model="model.password" type="password"></KInput>
</KFormItem>
</KForm>
<KFormItem>
<button @click="onLogin">登錄</button>
</KFormItem>
{{model}}
</div>
</template>
<script>
import KInput from "./KInput";
import KFormItem from "./KFormItem";
import KForm from "./KForm";
export default {
name: "HelloWorld",
data() {
return {
model: {
username: "tom",
password: ""
},
rules: {
username: [{ required: true, message: "用戶名必填" }],
password: [{ required: true, message: "密碼必填" }]
}
};
},
components: {
KInput,
KFormItem,
KForm
},
methods:{
onLogin(){
this.$refs.loginForm.validate((isValid)=>{
if (isValid) {
}else{
console.log('有錯(cuò)');
}
})
}
}
};
</script>
<style scoped></style>
//KForm.vue
<template>
<div>
<slot></slot>
</div>
</template>
<script>
export default {
props: {
model: {
type: Object,
required: true
},
rules: {
type: Object
}
},
provide() {
return {
form: this //傳遞form實(shí)例給后代辆它,比如formitem用來校驗(yàn)
}
},
methods: {
validate(cb) {
//之所以要filter是因?yàn)閮?nèi)部有些節(jié)點(diǎn)不需要校驗(yàn)誊薄。例如登錄,必須有prop屬性才需要校驗(yàn)
//注意此處不夠健壯
//map結(jié)果是若干promise結(jié)果
const tasks = this.$children
.filter(item => item.prop)
.map(item => item.validate());
//如果沒有任何一個(gè)有異常锰茉,則會走到then,否則不會進(jìn)入
Promise.all(tasks)
.then(() => {
cb(true);
})
.catch(() => cb(false));
}
}
};
</script>
<style>
</style>
//KFormItem.vue
<template>
<div>
<label v-if="label">
{{label}}
</label>
<slot></slot>
<!-- 校驗(yàn)信息 -->
<p v-if="errorMessage">{{errorMessage}}</p>
</div>
</template>
<script>
import Schema from "async-validator";
export default {
data(){
return{
errorMessage:''
}
},
props:{
label:{
type:String,
default:''
},
prop:String
},
//有些版本這么寫就行呢蔫,但是有些早期vue版本需要下面的寫法
// inject:["form"],
inject:{
form:{default:{}}
},
mounted(){
this.$on('validate',()=>{
this.validate()
})
},
methods:{
validate(){
//執(zhí)行組件校驗(yàn)
//1.獲取校驗(yàn)規(guī)則
const rules= this.form.rules[this.prop];
//2.獲取數(shù)據(jù)
const value= this.form.model[this.prop];
//3. 執(zhí)行校驗(yàn)-此時(shí)通過校驗(yàn)庫去校驗(yàn)-async-validator element就是用的這個(gè)庫
const desc={
[this.prop]:rules
}
const schema=new Schema(desc);
schema.validate({
[this.prop]:value
},errors=>{
if (errors) {
this.errorMessage=errors[0].message;
}else{
this.errorMessage='';
}
})
}
}
}
</script>
<style>
</style>
//KIput.vue
<template>
<div>
<!-- $attrs是存儲的props之外的東西,v-bind就是把index.vue在Kinput其他屬性展開飒筑,自然就會把type等傳遞過來 -->
<input :value="value" @input="onInput" v-bind="$attrs">
</div>
</template>
<script>
export default {
props:{
value:{
type:String,
default:''
}
},
inheritAttrs:false,//避免頂層容器繼承屬性片吊,例如當(dāng)外面type有類型password的時(shí)候,此div會繼承這些類似的屬性协屡,false則不會繼承
methods:{
onInput(e){
//通知父組件數(shù)值變化,但是實(shí)際上此時(shí)是該組件監(jiān)聽該組件通知俏脊,之所以這樣還麻煩的這么寫,是因?yàn)榉蟰ue的單向數(shù)據(jù)流肤晓,此時(shí)就是從父級到子
this.$emit('input',e.target.value)
//通知FormItem校驗(yàn)
this.$parent.$emit('validate');
}
}
}
</script>
<style></style>
單向數(shù)據(jù)流的優(yōu)勢:把副作用分離出來爷贫,好寫測試,假如子組件可以修改父組件的數(shù)據(jù)补憾,那么會導(dǎo)致其他依賴父組件的子組件都會受到影響
自定義組件的v-model
以下來自于官方文檔
一個(gè)組件上的 v-model 默認(rèn)會利用名為 value 的 prop 和名為 input 的事件漫萄,但是像單選框、復(fù)選框等類型的輸入控件可能會將 value attribute 用于不同的目的盈匾。model選項(xiàng)可以用來避免這樣的沖突:
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
現(xiàn)在在這個(gè)組件上使用 v-model 的時(shí)候:
<base-checkbox v-model="lovingVue"></base-checkbox>
這里的 lovingVue 的值將會傳入這個(gè)名為 checked 的 prop腾务。同時(shí)當(dāng) <base-checkbox> 觸發(fā)一個(gè) change 事件并附帶一個(gè)新的值的時(shí)候,這個(gè) lovingVue 的 property 將會被更新削饵。
說明:意思在于岩瘦,v-model不光可以用于input事件,可以通過自定義綁定任何事件
補(bǔ)充:@click.native的使用場景窿撬?記不住百度下
自定義提示組件
//create.js
import Vue from "vue";
//創(chuàng)建指定的組件實(shí)例启昧,并掛載于body上面
export default function create(Component, props) {
//1. 創(chuàng)建組件實(shí)例
const vm = new Vue({
render(h) {
//h createlement的簡稱
return h(Component, { props })
}
}).$mount();
const comp = vm.$children[0];
//追加dom到body,注意comp是組件實(shí)例,不是html字符串不可以直接放入dom
document.body.appendChild(vm.$el);
//清理
comp.remove = () => {
document.body.removeChild(vm.$el);
vm.$destroy();
}
return comp;
}
<template>
<div v-if="isShow">
<h3>{{title}}</h3>
<p>{{message}}</p>
</div>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ""
},
message: {
type: String,
default: ""
},
duration: {
type: Number,
default: 0
}
},
data() {
return {
isShow: false
};
},
methods: {
show() {
this.isShow = true;
setTimeout(() => {
this.hide();
}, this.duration);
},
hide() {
this.isShow = false;
this.remove();
}
}
};
</script>
<style>
</style>
簡化版vue-router
let Vue;
class VueRouter {
constructor(options) {
this.$options = options;
//創(chuàng)建一個(gè)路由path和route映射
this.routeMap = {};
//記錄當(dāng)前路徑current需要響應(yīng)式劈伴,利用vue響應(yīng)式原理可以做到這點(diǎn)
this.app = new Vue({
data: {
current: '/'
}
})
}
init() {
//綁定瀏覽器的事件
this.bindEvent();
//解析路由配置
this.createRouteMap(this.$options);
//創(chuàng)建router-link和router-view
this.initComponent();
}
bindEvent() {
//如果不綁定this箫津,則onHashChange內(nèi)部this就是windows了,綁定了就是VueRouter實(shí)例
window.addEventListener('hashchange', this.onHashChange.bind(this));
window.addEventListener('load', this.onHashChange.bind(this));
}
onHashChange() {
this.app.current = window.location.hash.slice(1) || '/';
}
createRouteMap(options) {
options.routes.forEach(item => {
//['/home']:{path:'/home',component:Home}
this.routeMap[item.path] = item;
})
}
initComponent() {
//注冊兩個(gè)到全局,都可以使用
Vue.component('router-link', {
props: {
to: String
},
//此時(shí)沒有箭頭苏遥,則this指向就是router-link自己饼拍,所以this.$slots.default就是router-link包裹的東西
render(h) {
//目標(biāo)是:<a :href="to">xxxx</a>
// return <a href={this.to}>{this.$slots.default}</a>
return h('a', { attrs: { href: '#' + this.to } }, this.$slots.default) //this.$slots.default其實(shí)就是默認(rèn)插槽,此時(shí)是xxxx
}
})
//hash->current->render
Vue.component('router-view', {
//箭頭函數(shù)保留this指向田炭,當(dāng)前this就是vuerouter的實(shí)例
render:(h)=> {
const Comp=this.routeMap[this.app.current].component;
// console.log(1111,h(Comp));//VNode....
return h(Comp);
}
})
}
}
//把VueRouter變?yōu)椴寮?組件實(shí)現(xiàn)install就變?yōu)椴寮?VueRouter.install = function (_Vue) {
//Vue.use的時(shí)候其實(shí)就是傳遞過來vue的構(gòu)造函數(shù)
Vue = _Vue;
//混入任務(wù)
Vue.mixin({
beforeCreate() {
//這里的代碼將來會在外面初始化的時(shí)候被調(diào)用,這樣就實(shí)現(xiàn)了vue的擴(kuò)展
//this就是vue的實(shí)例,所有vue實(shí)例都會執(zhí)行师抄,但是這時(shí)候只希望根組件執(zhí)行一次
if (this.$options.router) {
/**
* new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
其實(shí)就是這個(gè)router
*/
Vue.prototype.$router = this.$options.router;
//此時(shí)是全局混入,再最外面Vue實(shí)例化時(shí)候beforeCreate時(shí)候調(diào)用教硫,全局混入在所有beforeCreate最先執(zhí)行
this.$options.router.init();
}
}
})
}
//但是注意:不是所有的插件都需要混入實(shí)現(xiàn)叨吮,此時(shí)是因?yàn)樘厥獾囊藐P(guān)系和調(diào)用時(shí)機(jī)
export default VueRouter;
//Krouter.js
import HelloWorld from "@/components/HelloWorld";
import VueRouter from "./VueRouter";
import Vue from 'vue'
Vue.use(VueRouter);
export default new VueRouter({
routes:[
{path:'/',component:HelloWorld}
]
})
//main.js
import router from "./utils/krouter";
說明:這一行導(dǎo)入,在krouter中Vue.use(VueRouter)先執(zhí)行瞬矩,new VueRouter后執(zhí)行茶鉴,但是實(shí)際上,VueRouter.install內(nèi)部的混入是在beforeCreate才執(zhí)行的景用,所以順序上new VueRouter先于混入內(nèi)部的this.$options.router.init();
遞歸組件
遞歸組件其實(shí)就是內(nèi)部還有自己涵叮,有兩個(gè)條件,必須有name伞插,同時(shí)必須有結(jié)束條件
<template>
<div>
<h3>{{data.title}}</h3>
<!-- 必須有結(jié)束條件 -->
<Node v-for="d in data.children" :key="d.id" :data="d"></Node>
</div>
</template> <script>
export default {
name: "Node", // name對遞歸組件是必要的
props: {
data: {
type: Object,
require: true
}
}
};
// 使?
// <Node :data="{id:'1',title:'遞歸組件',children:[{...}]}"></Node>
</script>
路由守衛(wèi)
全局守衛(wèi)割粮,router.js
// 路由配置
{
path: "/about",
name: "about",
// 需要認(rèn)證,注意此時(shí)這個(gè)不是真的完成邏輯了,只能看作一個(gè)標(biāo)記位
meta: { auth: true },
component: () => import(/* webpackChunkName: "about" */
"./views/About.vue")
}
// 守衛(wèi)
router.beforeEach((to, from, next) => {
// 要訪問/about且未登錄需要去登錄
if (to.meta.auth && !window.isLogin) {
if (window.confirm("請登錄")) {
window.isLogin = true;
next(); // 登錄成功媚污,繼續(xù)
} else {
next('/');// 放棄登錄舀瓢,回??
}
} else {
next(); // 不需登錄,繼續(xù)
}
});
路由獨(dú)享守衛(wèi)
可以在某條路由內(nèi)部寫這個(gè)鉤子
beforeEnter(to, from, next) {
// 路由內(nèi)部知道??需要認(rèn)證
if (!window.isLogin) {
// ...
} else {
next();
}
}
組件內(nèi)的守衛(wèi)
export default {
beforeRouteEnter(to, from, next) {},
beforeRouteUpdate(to, from, next) {},
beforeRouteLeave(to, from, next) {}
};
動態(tài)添加路由
利?$router.addRoutes()可以實(shí)現(xiàn)動態(tài)路由添加耗美,常?于?戶權(quán)限控制
// router.js
// 返回?cái)?shù)據(jù)可能是這樣的
//[{
// path: "/",
// name: "home",
// component: "Home", //Home
//}]
// 異步獲取路由
api.getRoutes().then(routes => {
const routeConfig = routes.map(route => mapComponent(route));
router.addRoutes(routeConfig);
})
// 映射關(guān)系
const compMap = {
//也可以直接 'Home':Home
//但是下面是懶加載形式京髓,更加優(yōu)雅而且避免不需要的打包
'Home': () => import("./view/Home.vue")
}
// 遞歸替換
function mapComponent(route) {
route.component = compMap[route.component];
if (route.children) {
route.children = route.children.map(child => mapComponent(child))
}
return route
}
面包屑實(shí)現(xiàn)
利?$route.matched可得到路由匹配數(shù)組,按順序解析可得路由層次關(guān)系
// Breadcrumb.vue
watch: {
$route() {
// [{name:'home',path:'/'},{name:'list',path:'/list'}]
console.log(this.$route.matched);
// ['home','list']
this.crumbData = this.$route.matched.map(m => m.name)
}
}
//如果無法觸發(fā):加上immediate:true //一開始執(zhí)行一次
徹底明白VUE修飾符sync
//父組件
<template>
<div>
<input type="button"
value="我是父組件中的按鈕"
@click="show">
<child @upIsShow="changeIsShow" v-show="isShow"/>
</div>
</template>
<script>
import child from "@/components/child"
export default {
data() {
return {
isShow:false
}
},
components:{
child
},
methods:{
show(){
this.isShow=true;
},
changeIsShow(bol){
this.isShow=bol;
}
}
}
</script>
//子組件
<template>
<div>
我是一個(gè)子組件商架,我在紅色的海洋里堰怨!
<input type="button" value="點(diǎn)我隱身" @click="upIsShow">
</div>
</template>
<script>
export default {
methods:{
upIsShow(){
this.$emit("upIsShow",false);
}
}
}
</script>
如果我要將父組件中的事@upIsShow修改為@update:isShow不違法吧:
<child @update:isShow="changeIsShow" v-show="isShow"/>
子組件的emit自然也要做對應(yīng)調(diào)整:
upIsShow(){
this.$emit("update:isShow",false);
}
那么如果現(xiàn)在我將父組件的changeIsShow直接寫成匿名函數(shù),也能運(yùn)行吧:
<child @update:isShow="function(bol){isShow=bol}" v-show="isShow"/>
現(xiàn)在我將那匿名函數(shù)改成箭頭函數(shù)甸私,不過分吧:
<child @update:isShow="bol=>isShow=bol" v-show="isShow"/>
最后我將上面那行代碼做最后一次修改:
<child :isShow.sync="isShow" v-show="isShow"/>
至此終于涉及到了sync了诚些。以上代碼:isShow.sync="isShow"其實(shí)是 @update:isShow="bol=>isShow=bol"語法糖飞傀。是其一種簡寫形式皇型。
完整代碼
//父組件
<template>
<div>
<input type="button"
value="我是父組件中的按鈕"
@click="show">
<child :isShow.sync="isShow" v-show="isShow"/>
</div>
</template>
<script>
import child from "@/components/child"
export default {
data() {
return {
isShow:false
}
},
components:{
child
},
methods:{
show(){
this.isShow=true;
},
changeIsShow(bol){
this.isShow=bol;
}
}
}
</script>
//子組件
<template>
<div>
我是一個(gè)子組件,我在紅色的海洋里砸烦!
<input type="button" value="點(diǎn)我隱身" @click="upIsShow">
</div>
</template>
<script>
export default {
methods:{
upIsShow(){
this.$emit("update:isShow",false);
}
}
}
</script>
如果沒有sync則父組件就需要在html上面寫函數(shù)或者在父組件method里面寫函數(shù)弃鸦,但是針對上面這種簡單通信的邏輯,其實(shí)sync可以省去父組件部分邏輯
插件
// 插件定義
MyPlugin.install = function (Vue, options) {
// 1. 添加全局?法或?qū)傩? Vue.myGlobalMethod = function () {
// 邏輯...
}
// 2. 添加全局資源
Vue.directive('my-directive', {
bind(el, binding, vnode, oldVnode) {
// 邏輯...
}
...
})
// 3. 注?組件選項(xiàng)
Vue.mixin({
created: function () {
// 邏輯...
}
...
})
// 4. 添加實(shí)例?法
Vue.prototype.$myMethod = function (methodOptions) {
// 邏輯...
}
}
// 插件使?
Vue.use(MyPlugin)
混入
// 定義?個(gè)混?對象
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// 定義?個(gè)使?混?對象的組件
var Component = Vue.extend({
mixins: [myMixin]
})
render函數(shù)詳解
?些場景中需要 JavaScript的完全編程的能?幢痘,這時(shí)可以?渲染函數(shù)唬格,它?模板更接近編譯器
render(h) {
return h(tag, { ...}, [children])
}
h其實(shí)就是createElement函數(shù)
{
// 與 `v-bind:class` 的 API 相同,
// 接受?個(gè)字符串、對象或字符串和對象組成的數(shù)組
'class': {
foo: true,
bar: false
},
// 與 `v-bind:style` 的 API 相同购岗,
// 接受?個(gè)字符串汰聋、對象,或?qū)ο蠼M成的數(shù)組
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 組件 prop
props: {
myProp: 'bar'
},
// DOM 屬性
domProps: {
innerHTML: 'baz'
},
// 事件監(jiān)聽器在 `on` 屬性內(nèi)喊积,
// 但不再?持如 `v-on:keyup.enter` 這樣的修飾器烹困。
// 需要在處理函數(shù)中?動檢查 keyCode。
on: {
click: this.clickHandler
},
}
函數(shù)式組件
組件若沒有管理任何狀態(tài)乾吻,也沒有監(jiān)聽任何傳遞給它的狀態(tài)髓梅,也沒有?命周期?法,只是?個(gè)接受?些prop 的绎签,可標(biāo)記為函數(shù)式組件枯饿,此時(shí)它沒有上下?
- props :提供所有 prop 的對象
- children : VNode ?節(jié)點(diǎn)的數(shù)組
- slots : ?個(gè)函數(shù),返回了包含所有插槽的對象
- scopedSlots : (2.6.0+) ?個(gè)暴露傳?的作?域插槽的對象诡必。也以函數(shù)形式暴露普通插槽奢方。
- data :傳遞給組件的整個(gè)數(shù)據(jù)對象,作為 createElement 的第?個(gè)參數(shù)傳?組件
- parent :對?組件的引?
- listeners : (2.3.0+) ?個(gè)包含了所有?組件為當(dāng)前組件注冊的事件監(jiān)聽器的對象擒权。這是
- data.on 的?個(gè)別名袱巨。
- injections : (2.3.0+) 如果使?了 inject 選項(xiàng),則該對象包含了應(yīng)當(dāng)被注?的屬性碳抄。
Vue.component('my-component', {
//標(biāo)記為函數(shù)式組件
functional: true,
// Props 是可選的
props: {
// ...
},
// 為了彌補(bǔ)缺少的實(shí)例
// 提供第二個(gè)參數(shù)作為上下文
render: function (createElement, context) {
// ...
}
})
Vuex數(shù)據(jù)管理
- Vuex 是?個(gè)專為 Vue.js 應(yīng)?開發(fā)的狀態(tài)管理模式愉老,集中式存儲管理應(yīng)?所有組件的狀態(tài)。
- Vuex遵循“單向數(shù)據(jù)流”理念剖效,易于問題追蹤以及提?代碼可維護(hù)性嫉入。
- Vue中多個(gè)視圖依賴于同?狀態(tài)時(shí),視圖間傳參和狀態(tài)同步?較困難璧尸,Vuex能夠很好解決該問題咒林。
核心概念
- state 狀態(tài)、數(shù)據(jù)
- mutations 更改狀態(tài)的函數(shù)
- actions 異步操作(只是可以存在異步操作爷光,例如網(wǎng)絡(luò)請求垫竞,但是一般不會把網(wǎng)絡(luò)放這里)
- store 包含以上概念的容器
狀態(tài)和狀態(tài)變更
state保存數(shù)據(jù)狀態(tài),mutations?于修改狀態(tài)蛀序,store.js
export default new Vuex.Store({
state: { count: 0 },
mutations: {
increment(state, n = 1) {
state.count += n;
}
}
})
使?狀態(tài)欢瞪,vuex/index.vue
<template>
<div>
<div>沖啊,?榴彈扔了{(lán){ $store.state.count }}個(gè)</div>
<button @click="add">扔?個(gè)</button>
</div>
</template > <script>
export default {
methods: {
add() {
this.$store.commit("increment");
}
}
};
</script>
派?狀態(tài) - getters
從state派?出新狀態(tài)徐裸,類似計(jì)算屬性
export default new Vuex.Store({
getters: {
score(state) {
return `共扔出:${state.count}`
}
}
})
登錄狀態(tài)?字遣鼓,App.vue
<span>{{$store.getters.score}}</span>
mutation喝action區(qū)別
mutations 類似于事件,用于提交 Vuex 中的狀態(tài) state
action 和 mutations 也很類似重贺,主要的區(qū)別在于mutations 只能是同步操作骑祟,回懦,action `==可以包含==`異步操作,而且可以通過 action 來提交 mutations
mutations 有一個(gè)固有參數(shù) state次企,接收的是 Vuex 中的 state 對象
action 也有一個(gè)固有參數(shù) context怯晕,但是 context 是 state 的父級,包含 state缸棵、getters等等
Vuex 的倉庫是 store.js贫贝,將 axios 引入,并在 action 添加新的方法
分發(fā)調(diào)用action:
this.$store.dispatch('action中的函數(shù)名'蛉谜,發(fā)送到action中的數(shù)據(jù))
在組件中提交 Mutation:
this.$store.commit(“mutation函數(shù)名”稚晚,發(fā)送到mutation中的數(shù)據(jù))
在action中提交mutation :
actions: {
increment (context) { //官方給出的指定對象, 此處context可以理解為store對象
context.commit('increment');
}
}
總之:
mutation使用是: this.$store.commit("add");
action使用是: this.$store.dispatch("addSync");
動作 - actions
復(fù)雜業(yè)務(wù)邏輯,類似于controller
export default new Vuex.Store({
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit("increment", 2);
}, 1000);
}
}
})
使?actions:
<template>
<div id="app">
<div>沖啊型诚,?榴彈扔了{(lán){ $store.state.count }}個(gè)</div>
<button @click="addAsync">蓄?扔倆</button>
</div>
</template > <script>
export default {
methods: {
addAsync() {
this.$store.dispatch("incrementAsync");
}
};
</script>
模塊化
按模塊化的?式編寫代碼客燕,store.js
const count = {
//必須開啟名命空間
namespaced: true,
// ...
};
export default new Vuex.Store({
modules: { a: count }
});
使?變化,components/vuex/module.vue
<template>
<div id="app">
<div>沖啊狰贯,?榴彈扔了{(lán){ $store.state.a.count }}個(gè)</div>
//注意此處是getters
<p>{{ $store.getters['a/score'] }}</p>
<button @click="add">扔?個(gè)</button>
<button @click="addAsync">蓄?扔倆</button>
</div >
</template > <script>
export default {
methods: {
add() {
this.$store.commit("a/increment");
},
addAsync() {
this.$store.dispatch("a/incrementAsync");
}
}
};
</script>
補(bǔ)充:十分重要
Vue.component("comp", {
/**
* 瀏覽情況下才能使用字符串模板也搓,因?yàn)闉g覽器直接使用vue是通過script引入的帶有運(yùn)行時(shí)編譯器的js版本,所以才能解析字符串模板
* webpack情況下涵紊,是不能使用template字符串這種形式的
*
* 但是不光這種方式(第二種)可以用傍妒,因?yàn)閣ebpack配置的有babel-loader所以jsx也可以使用
*/
// template:'<div id="box" class="foo><sapn>aaa</span></div>',
render(h) {
return h("div", {
class: { foo: true}, attrs: { id: "box" } }, [
h("span", "aaa")
]);
}
});
自定義實(shí)現(xiàn)簡化版Vuex
//維護(hù)狀態(tài) state
//修改狀態(tài) commit
// 業(yè)務(wù)邏輯的控制 dispatch
//狀態(tài)派發(fā) getter
//實(shí)現(xiàn)state的響應(yīng)式
//實(shí)現(xiàn)插件
//實(shí)現(xiàn)混入
let Vue;
function install(_Vue, storeName = '$store') {
Vue = _Vue;
//混入:把store選項(xiàng)指定到Vue原型上
Vue.mixin({
beforeCreate() {
if (this.$options.store) {
Vue.prototype[storeName] = this.$options.store;
}
}
})
}
class Store {
//options其實(shí)就是{state:{count:0}}類似這種
constructor(options = {}) {
//利用vue的數(shù)據(jù)響應(yīng)式
this.state = new Vue({
data: options.state
})
//初始化mutations
this.mutations = options.mutations || {};
this.actions = options.actions || {};
options.getters && this.hadleGetters(options.getters);
}
//觸發(fā)mutations,需要實(shí)現(xiàn)commit
//箭頭函數(shù)摸柄,this指向store實(shí)例
commit = (type, arg) => {
const fn = this.mutations[type];//獲取狀態(tài)變更函數(shù)
fn(this.state, arg);
}
dispatch(type, arg) {
const fn = this.actions[type];
return fn({ commit: this.commit, state: this.state }, arg);
}
hadleGetters(getter) {
this.getters = {}
//定義只讀的屬性
Object.keys(getter).forEach(key => {
Object.defineProperty(this.getters, key, {
get: () => {
return getter[key](this.state);
}
})
})
}
}
//Vuex
export default { Store, install }
使用
import Vue from "vue";
import KVuex from "./Kvuex";
Vue.use(KVuex);
export default new KVuex.Store({
state:{
count:0
},
mutations:{
add(state,num=1){
state.count+=num;
}
},
actions:{
addSyc({commit}){
return new Promise(resolve=>{
setTimeout(()=>{
commit("add");
resolve({ok:1})
},1000)
})
}
},
getters:{
score(state){
return "score: "+state.count;
}
}
})
//main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './Kindex'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
說明:雖然一般官方建議都是通過mutation去修改state颤练,但是實(shí)際上直接操作state也可以修改,但是不建議驱负,因?yàn)檫@樣破化了單向數(shù)據(jù)流嗦玖,不方便測試同時(shí)可以分離副作用