坚持周总结系列第三周(Vue)

坚持周总结系列第三周(2020.4.30)

Vue

Vue组件化常用技术

组件传值、通信

父组件给子组件传值

  • 属性props
// child
props:{msg:String}
// partent
<Child msg='hello world'></Child>
  • refs引用
// parent
<Child ref='child'></Child>
this.$refs.child.xx

子组件给父组件传值

// child
this.$emit('child','child message')
// parent
<Child @child='showChildMessage'></Child>

兄弟组件传值

通过共同的祖辈组件搭桥, p a r e n t 或 parent或 parentroot

// brother1
this.$parent.$on('brotherFun',brotherMessage=>{console.log(brotherMessage)})
// brother1
this.$parent.$emit('brotherFun','brotherMessage')

祖代和后代之间传值

  • provide/inject:能够实现祖先给后代传值
// ancestor
provider(){
    return {
        message:'hello world!'
    }
}
// descendant
inject:['message']
  • 自定义dispatch实现后代给祖先传值
// 定义一个dispatch方法
function dispatch(eventName,data){
    let parent=this.$parent
    // 只要还存在父元素就继续往上找
    while(parent){
        // 父元素用$emit触发
        parent.$emit(eventName,data)
        // 递归查找父元素
        parent=parent.$parent
    }
}
// 使用 descendant
<h1 @click="dispatch('message','hello world!')">给祖先传值</h1>
// ancestor
this.$on('message',message=>{console.log(message)})

任意两个组件之间

  • 事件总线:创建一个Bus类负责事件派发、监听和回调管理
// Bus:事件派发、监听和回调管理
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.callbakcs[name].forEach(cb=>cb(args))
        }
    }
}
// main.js
Vue.prototype.$bus=new Bus()
// child1
this.$bus.$on('message',message=>{
    console.log(message)
})
// child2
this.$bus.$emit('message','hello world')

插槽

匿名插槽

// child
<div>
    <slot><slot>
</div>
// parent
<Child>hello world!</Child>

具名插槽

Vue2.6.0之后采用全新的v-slot语法取代之前的slot、slot-scope

// child
<div>
	<slot></slot>
	<slot name='content'></slot>
</div>
// parent
<Child>
    <!-- 默认插槽用default做参数 -->
    <template v-slot:default>默认插槽</template>
	<template v-slot:content>内容部分...</template>
<Child>

作用域插槽

// child
<div>
	<slot :message='hello world'></slot>    
</div>
// parent
<Child>
	<template v-slot:default='child'>
		来自子组件的数据:{child.message}
	</template>
</Child>

数据双向绑定

  • v-model
// v-model 是语法糖
<Input v-model='username'>
// 默认等效于下面代码
<Input :value='username' @input='usernameHandle'>

  • .sync
// sync修饰符类似于v-model,他能用于修改传递到子组件的属性
<Input :value.sync='username'>
// 等效于下面代码
<Input :value='username' @update:value='usernameHandle'>

Vue-router使用

  • 路由配置
routes:[
    {
        path:'/',
        name:'home',
        component:Home
    },
    {
        path:'/about',
        name:'about',
        // 路由层级的代码分割,webpack打包时会生成 about.[hash].js 独立文件
        // 路由访问时模块会懒加载
        component:()=>import(/*webpackChunkName:"about"*/'../views/About.vue')
    }
]

  • 挂载路由器
// main.js
new Vue({
    router,
    render:h=>h(App)
}).$mount('#app')

  • 路由视图
<router-view />

  • 路由导航
<router-link to='/'>首页</router-link>
<router-link to='/about'>关于我们</router-link>

  • 路由嵌套
// router.js
{
    path:'/',
    component:Home,
    children:[{
        path:'/list',
        name:'list',
        component:List
    }]
}
// Home.vue
<template>
	<div class='home'>
    	<h1>首页</h1>
		<router-view />
    </div>    
</template>

  • 动态路由
// params传参
// 路由配置
{path:'detail/:id',name:'detail',component:Detail}
// 路由传参
this.$router.push({name:'detail',params:{id:101}})
// 接收路由参数
console.log(this.$route.params.id)

// query传参
this.$router.push({
    path:'/detail',
    query:{
        id:101
    }
})
// 接受参数
console.log(this.$router.query.id)

// router-link 传参
<router-link :to="{path:'/detail',query:{id:101}}"></router-link>
<router-link :to="{name:'detail',params:{id:101}}"></router-link>

  • 路由守卫
// 全局路由守卫 router.js
router.beforeEach((to,from,next){
   // 需要访问about页面,如果未登录需要先去登录
   if(to.meta.auth && !window.isLogin){
        window.isLogin=true
        // 登陆成功,继续访问页面
        next()
    }else{
        // 不需要登录,直接访问页面
        next()
    }
})

// 路由独享守卫 写在路由配置里面
{
    path:'/about',
    component:About,
    beforeEnter(to,from,next){
        if(!window.login){
            // 去登陆
        }esle{
            next()
        }
    }
}

// 组件内的路由守卫 写在组件里面
export default{
    beforeRouterEnter(to,from,next){
        // this不能用
    },
    beforeRouterUpdate(to,from,next){...},
    beforeRouterLeave(to,from,next){...}
}

  • 根据角色权限,动态配置路由
// 异步获取路由 
// 后台存的方式:{path:'/',component:'Home',children:[{path:'/detail',component:'Detail'}]}
api.getRoutes().then(routes=>{
    const routeConfig = routes.map(route => mapComponent(route));
    router.addRoutes(routerConfig)
})
// 路由映射
const compMap={
    'Home':()=>import('../views/Home.vue')
}
// 递归替换
function mapComponent(route){
    route.component=compMap[route.component]
    if(route.children){
        route.children=route.children.map(chils=>mapComponent(child))
    }
    return route
}

vuex使用

  • 状态和变更
export default new Vuex.Store({
    state:{count:0},
    mutations:{
        increment(state,n=1){
            state.count += 1
        }
    }
})

  • 使用状态
<div>冲啊,手榴弹扔了{{$store.state.count}}<div>
<button @click='add'>再扔一个</button>
...
methods:{
    add(){
        this.$store.commit('increment')
    }
}

  • 派生状态
getters:{
    score(state){
        return `一共扔了${state.count}个`
    }
}

  • 使用getters
<span>{{$store.getters.score}}</span>

  • 动作actions
actions:{
    incrementAsync({commit}){
        setTimeout(()=>{// 模拟异步操作
            commit('increment',2)
        },1000)
    }
}

  • 使用actions
<button @click='addAsync'>蓄力扔两个</button>
...
methods:{
    addAsync(){
        this.$store.dispatch('incrementAsync')
    }
}

  • 模块化
const count ={
    namespaced:true,
    state:{...},
    getters:{...},
    mititions:{...},
    actions:{...}
}
export default new Vuex.Store({
	modules:{a:count}
})

  • 使用模块化的store
<div>冲啊,手榴弹扔了{{$store.state.a.count}}</div>
<p>{{$store.gettersp['a/score']}}</p>
<button @click='add'>扔一个</button>
<button @click='addAsync'>蓄力扔两</button>
...
methods:{
    add(){
        this.$store.commit('a/increment')
    },
    addAsync(){
        this.$store.dispatch('a/incrementAsync')
    }
}

  • 使用辅助函数
<div>冲啊,手榴弹扔了{{count}}</div>
<p>{{score}}</p>
<button @click='add'>扔一个</button>
<button @click='addAsync'>蓄力扔两</button>
...
computed: {
    ...mapState('a', {
      count: state => state.count
    }),
    ...mapGetters('a', {
      score: 'score'
    })
},
methods: {
    ...mapMutations('a', ['increment']),
    ...mapActions('a', ['incrementAsync']),
    add () {
      this.increment()
    },
    addAsync () {
      this.incrementAsync()
    }
}

Vue插件

vue.use

接收一个对象或者方法

  • 如果传入的是方法——执行这个方法
  • 如果传入的是一个对象,里面有install方法——执行install方法

vue.imxin

  • 搭配vue.use使用,扩展vue实例
  • 接收对象做参数
  • vue实例混入data、生命周期、methods等

使用插件机制实现动态加载vuex

// myPlugins/index.js
module.exports={
    install:function(Vue){
        Vue.mixin({
            created(){
                if(this.$options.isVuex){
                    // import不能接收变量,只能接收字符串
                    import('../store/modules/'+this.$options.name).then(res=>{
                        this.$store.registerModule(this.$options.name,res.default)
                    })
                }
            }
        })
    }
}
// 使用 mian.js
import plugins from './myPlugins'
Vue.use(plugins)

Vue按钮级别权限控制

  • 封装一个指令 v-premission,从而实现按钮级别权限控制
import store from '@/store'

const permission={
    inserted(el,binding){
        // 获取指令的值:按钮要求的角色数组
        const {value:pRoles}=binding
        // 获取用户角色
        const roles=store.getters && store.getters.roles
        if(pRoles && pRoles instanceof Array && pRoles.length>0){
            // 判断用户角色中是否有按钮要求的角色
            const hasPermission=roles.some(rols=>{
                return pRoles.includes(role)
            })
            // 如果没有权限删除当前DOM
            if(!hasPermission){
                el.parentNode && el.parentNode.removeChild(el)
            }
        }else{
            throw new Error(`请指定按钮要求的角色权限,如v-permission="['admin','editor']"`)
        }
    }
}

export default permission

  • 注册制定
import vPermission from './directive/permission'
Vue.directive('permission',vPermission)

  • 使用自定义指令
// 给按钮添加权限
<button v-permission="['admin','editor']">编辑</button>
<button v-permission="['admin']">管理</button>

vue-cli 3.0项目自定义webpack配置

  • 在根目录创建vue.config.js
// vue.config.js
const port=3000
const title='webpack'
const path=require('path')

function resolve(dir){
    return path.join(__dirname,dir)
}

module.exports={
    publicPath:'',// 部署应用时的基本URL
    devServer:{
        port:port
    },
    configureWebpack:{
        name:title, // index.html 使用:<%= webpackConfig.name %>
        plugins:[
            new MyWebpackPlugin()
        ]
    },
    chainWebpack(config){
        // 配置svg规则排除icons目录中svg文件处理
        config.module
        	.rule('svg')
        	.exclude.add(resolve('src/icons'))
        	.end()
        // 新增icons规则,设置svg-sprite-loader处理icons目录中的svg
        config.module
        	.rule('icons')
        	.test(/\.svg$/)
        	.include.add(resolve('src/icons'))
        	.end()
        	.use('svg-sprite-loader')
        	.loader('svg-sprite-loader')
        	.options({symbolId:'icon-[name]'})
        	.end()
    }
}

  • svg图标自动导入
// icons/index.js
const req=require.context('./svg',false,/\.svg$/)
req.keys().map(req)

// main.js
import './icons'

  • 创建SvgIcon组件
// components/SvgIcon.vue
<template>
    <svg :class='svgClass' aria-hidden='true' v-on='$listeners'>
    	<use :xlink:href='iconName' />
    </svg>
</template>
<script>
export default {
	name:'SvgIcon',
    props:{
        iconClass:{
            type:String,
            required:true
        },
        className:{
            type:String,
            default:''
        }
    },
    computed:{
        iconName(){
            return `#icon-${this.iconClass}`
        },
        svgClass(){
            if(this.className){
                return 'svg-icon'+this.className
            }else{
                return 'svg-icon'
            }
        }
    }
}
</script>
<style>
.svg-icon{
    width:1em;
    height:1em;
    vertical-align:-0.15em;
    fill:currentColor;
    overflow:hidden;
}    
</style>

  • 注册SvgIcon
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'

Vue.component('svg-icon',SvgIcon)

  • 使用SvgIcon
<svg-icon icon-class='qq'></svg-icon>

  • 要添加新的图标,只需要下载对应的svg图标,放到src/icons/svg下面,就可以在组件中使用了

你可能感兴趣的:(坚持周总结)