vue3笔记

路由

1.配置

// 导入创建路由
// createWebHashHistory返回hash路由模式
// createWebHistory返回history模式
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
const routes = [{
        path: '/',
        component: () =>
            import ('../views/Home.vue')
    }, {
        path: '/about/:id',
        component: () =>
            import ('../views/About.vue')
    }]
    // 创建一个路由器对象
const router = createRouter({
    // 指定路由模式
    history: createWebHashHistory(),
    // 指定路由信息
    routes
})
export default router

2.导入

// 导入路由
import router from './router'
// use方法使用路由
createApp(App).use(router).mount('#app')

3.使用

// useRoute可以获取到路由的id
// useRouter是路由器对象,push可以跳转
import { useRoute,useRouter } from 'vue-router'
// useRoute的使用
 setup() {
        let citys = [{
            id: 1,
            name: '南京',
            content: '鸭血粉丝'
        }, {
            id: 2,
            name: '北京',
            content: '烤鸭'
        }, {
            id: 3,
            name: '广州',
            content: '肠粉'
        },]
        // 声明route
        let route = useRoute()
        //  获取传过来的id
        let id = ref(route.params.id)

        // 找到id对应的城市信息
        let city = computed(() => {
            return citys.find(r => r.id == id.value)
        })
        console.log(city);
        // 修复不切换的bug,监视路由id变化,将val重新赋值给id
        watch(() => route.params.id, (val) => {
            id.value = val
        })
        return {
            city
        }
    }

路由案例 动态路由

创建路由

import { createRouter, createWebHashHistory } from "vue-router";
// 导入导航条
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
// 创建路由信息

const routes = [
    // 登录
    {
        path: '/',
        meta: {
            title: '登录'
        },
        component: () =>
            import ('../views/Login.vue')
    },
    // 管理
    {
        path: '/layout',
        meta: {
            title: '管理'
        },
        component: () =>
            import ('../views/Layout.vue'),
        children: [{
                path: 'index',
                meta: {
                    title: '首页',
                    role: [1, 2]
                },
                component: () =>
                    import ('../views/Home.vue')
            },
            {
                path: 'about',
                meta: {
                    title: '关于',
                    role: [1, 2]
                },
                component: () =>
                    import ('../views/About.vue')
            }, {
                path: 'car',
                meta: {
                    title: '汽车',
                    role: [1]
                },
                component: () =>
                    import ('../views/Car.vue')
            }, {
                path: 'phone',
                meta: {
                    title: '手机',
                    role: [1]
                },
                component: () =>
                    import ('../views/Phone.vue')
            }, {
                path: 'plane',
                meta: {
                    title: '飞机',
                    role: [1]
                },
                component: () =>
                    import ('../views/Plane.vue')
            },
        ]
    },
    // 403
    {
        path: '/error403',
        meta: {
            title: '权限不足'
        },
        component: () =>
            import ('../views/Error403.vue')
    },
    // 404
    {
        path: '/:pathMatch(.*)*',
        meta: {
            title: '页面不存在'
        },
        component: () =>
            import ('../views/Error404.vue')
    }
]


// 动态路由写法  动态路由在开发环境刷新会丢失状态,需要切换到普通路由
// const routes = [
//     // 登录
//     {
//         path: '/',
//         meta: {
//             title: '登录'
//         },
//         component: () =>
//             import ('../views/Login.vue')
//     },
//     // 管理
//     {
//         path: '/layout',
//         name: 'Layout',
//         meta: {
//             title: '管理'
//         },
//         component: () =>
//             import ('../views/Layout.vue'),
//         children: []
//     },
//     // 403
//     {
//         path: "/error403",
//         meta: {
//             title: "权限不足",
//         },
//         component: () =>
//             import ("../views/Error403.vue"),
//     },
//     // 404
//     {
//         // v3固定写法
//         path: '/:pathMatch(.*)*',
//         meta: {
//             title: '页面不存在'
//         },
//         component: () =>
//             import ('../views/Error404.vue')
//     }
// ]
const router = createRouter({
        history: createWebHashHistory(),
        routes
    })
    // 定义一个添加动态路由信息的方法
    // export const loadRouter = (qx) => {
    //         qx.forEach(r => {
    //             router.addRoute('Layout', {
    //                 path: r.path,
    //                 meta: r.meta,
    //                 // 拿到名字后动态拼接路由
    //                 component: () =>
    //                     import ('../views/' + r.component + '.vue')
    //             })
    //         })
    //     }
    // 路由前置守卫
router.beforeEach((to, from, next) => {
        NProgress.start();
        // 获取当前用户信息
        let currentUser = JSON.parse(sessionStorage.getItem('currentUser'))
            // 验证当前用户是否可以跳转到指定的路由
        if (to.meta.role) {
            if (to.meta.role.includes(currentUser.roleId)) {
                next();
            } else {
                // 权限不足跳转到403
                router.push('/error403')
            }

        }
        // 登录页面不用权限
        if (!to.meta.role) {
            next()
        }
    })
    //路由后置守卫
router.afterEach((to, from) => {
    NProgress.done();
    document.title = to.meta.title
})
export default router

登录界面

<template>
    <div class="login">
        <div class="item">
            <span>账号:</span>
            <input type="text" v-model="user.loginId" />
        </div>
        <div class="item">
            <span>密码:</span>
            <input type="password" v-model="user.password" />
        </div>
        <div class="item">
            <button @click="login">登录</button>
        </div>
    </div>
</template>
<script>
import { useRouter } from 'vue-router'
import { reactive } from 'vue'
// 导入动态加载路由信息的方法
import { loadRouter } from '../router'
export default {
    name: 'Login',
    setup() {
        // 权限
        let quanxian = [{
            path: 'index',
            meta: {
                title: '首页',
                // 角色
                role: [1, 2]
            },
            component: "Home"
        },
        {
            path: 'about',
            meta: {
                title: '关于',
                // 角色
                role: [1, 2]
            },
            component: 'About'
        }, {
            path: 'car',
            meta: {
                title: '汽车',
                role: [1]
            },
            component: 'Car'
        }, {
            path: 'phone',
            meta: {
                title: '手机',
                role: [1]
            },
            component: 'Phone'
        }, {
            path: 'plane',
            meta: {
                title: '飞机',
                role: [1]
            },
            component: 'Plane'
        }]


        // 用户权限的信息  实际在后台返回
        let users = [
            {
                loginId: 'admin',
                password: '123',
                roleId: 1
            }, {
                loginId: 'jack',
                password: '123',
                roleId: 2
            }
        ]
        // 登录对象
        let user = reactive({
            loginId: '',
            password: '',
        })
        // 登录事件
        let router = useRouter()
        function login() {
            // 第一步 获取当前用户输入的信息与数据库是否匹配
            let currentUser = users.find(r => r.loginId == user.loginId && r.password == user.password)
            // 判断 如果权限OK
            if (currentUser) {
                // 如果权限OK将当前用户的信息缓存到浏览器
                sessionStorage.setItem('currentUser', JSON.stringify(currentUser))
                // 再去路由数组里面找到用户的权限
                let qx = quanxian.filter(r => r.meta.role.includes(currentUser.roleId))
                // 动态路由方法  开发环境刷新会丢失状态 所以关掉
                // loadRouter(qx)
                // 再将用户的权限缓存到浏览器 JSON.stringify(qx)转换为字符串
                sessionStorage.setItem('qx', JSON.stringify(qx))
                // 验证完开始跳转
                router.push('/layout')
            } else {
                alert('登录失败')
            }
        }
        return {
            login, users, user, quanxian
        }
    }
}
</script>
<style scoped >
.login {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto;
    border: 1px solid #ccc;
    width: 300px;
    height: 130px;
    background: lightblue;
}
.login .item {
    padding: 10px;
}
</style>

登录成功后界面

<template>
    <div class="layout">
        <div class="left">
            <div class="item" v-for="(item, index) in qx" :key="index">
                <router-link :to="'/layout/' + item.path">{{ item.meta.title }}</router-link>
            </div>
        </div>
        <div class="right">
            <!-- 二级路由 -->
            <router-view></router-view>
        </div>
    </div>
</template>
<script>

import { reactive } from 'vue'
import { useRouter } from 'vue-router'
export default {
    name: 'Layout',
    setup() {
        //获取浏览器缓存中的权限信息  根据权限展示相应的菜单
        let qx = reactive(JSON.parse(sessionStorage.getItem('qx')))
        // 获取当前用户信息
        let currentUser = JSON.parse(sessionStorage.getItem('currentUser'))

        let router = useRouter()
        if (!currentUser) {
            // 如果是非登录用户,跳转到登录页
            router.push('/')
        }
        return {
            qx
        }
    }
}
</script>
<style scoped >
.layout {
    display: flex;
}
.left {
    width: 200px;
    height: 600px;
    background: lightblue;
}
.left .item {
    text-align: center;
    padding: 20px 0;
}
.left .item a {
    color: black;
}
.right {
    margin-left: 15px;
}
</style>

vuex

1.创建vuex

import { createStore } from "vuex";
import car from './car'
// import axios from 'axios'
export default createStore({
    state: {
        stus: [{
            no: 1001,
            name: '张三',
            age: 19,
            sex: '男'
        }, {
            no: 1002,
            name: '李四',
            age: 21,
            sex: '女'
        }, {
            no: 1003,
            name: '王五',
            age: 28,
            sex: '女'
        }, ]
    },
    getters: {
        avgAge(state) {
            let sumAge = state.stus.map(r => r.age).reduce((a, b) => a + b, 0)
            return (sumAge / state.stus.length).toFixed(2)
        }
    },
    mutations: {
        addStu(state, val) {
            state.stus.push(val)
        },
        delStu(state, val) {
            // find根据id找到对象,indexOf找到下标,splice根据下标删除
            state.stus.splice(state.stus.indexOf(state.stus.find(r => r.no == val)), 1)
        }
    },
    actions: {
        delStu(context, val) {
            setTimeout(() => {
                context.commit('delStu', val)
            }, 1000);
        }
    },
    modules: {
        car
    }
})

2.创建模块vuex

export default {
    namespaced: true,
    state: {
        cars: [{
            id: 1,
            name: '奔驰',
            color: '黑色',
            price: 100
        }, {
            id: 2,
            name: '奥迪',
            color: '黑色',
            price: 200
        }, {
            id: 3,
            name: '宾利',
            color: '黑色',
            price: 400
        }, ]
    },
    getters: {
        avgPrice(state) {
            let sumPrice = state.cars.map(r => r.price).reduce((a, b) => a + b, 0)
            return (sumPrice / state.cars.length).toFixed(2)

        }
    },
    mutations: {
        addCar(state, val) {
            state.cars.push(val)
            console.log(state.cars);
        },
        delCar(state, val) {
            state.cars.splice(val, 1)
        }
    },
    actions: {}
}

3. 使用Vuex

import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
    name: 'Home',
    setup() {
        let store = useStore()
        let stu = computed(() => store.state.stus)
        // 删除
        function delStu(id) {
            if (confirm('确定删除吗?')) {
                store.dispatch('delStu', id)
            }
        }
        // 平均年龄
        let avgAge = store.getters.avgAge
        return {
            stu, avgAge, delStu
        }
    }
}

4. 使用模块vuex

使用模块vuex要加前缀

 setup() {
        let store = useStore()
        // 获取数据第一种
        // let cars = store.state['car'].cars
        // 获取数据第二种
        let cars = computed(() => store.state.car.cars)
        // 获取计算属性
        let avgPrice = computed(() => store.getters['car/avgPrice'])
        let car = reactive({
            id: '',
            name: '',
            color: '',
            price: ''
        })
        // 获取方法
        function addCar() {
            store.commit('car/addCar', { ...car })
        }
        function delCar(index) {
            store.commit('car/delCar', index)
        }
        return {
            cars, avgPrice, car, addCar, delCar
        }
    }

computed

//    定义计算属性(只读)
 const phoneInfo=computed(()=>{
     return  phonename.value+'-'+price.value
 })
   //定义计算属性(可写)
   const phoneInfo=computed({
       get(){
           return phonename.value+'-'+price.value
       },
       set(val){
           let arr =val.split('-')
           phonename.value=arr[0]
           price.value=arr[1]
       }
   })
```
# watch
```js
// 三个参数,第一个参数是监视的属性,第二个是回调函数,第三个是配置对象:立即执行,深度监视
// 1.深度监视默认开启关不掉,如果监视的对象数据很多,对内存开销很大
   watch(stu,(nval,oval)=>{
       console.log(nval,oval)
   },{
       // immediate:true,
       // deep:false
   })
// 2.监视对象身上的属性,推荐用下面的方式
   watch(
     () => stu.age,
     (nval, oval) => {
       //    console.log(nval,oval);
     }
   );
// watchEffect定义监视哪个属性  在他内部声明后,但值发生变化后他会执行
watchEffect(() => {
     let age = stu.age;
     let pricr = stu.car.price;
     console.log("你好");
   });
```


瞬移组件

// <teleport >组件瞬移,改变组件的父级,适用于定位模态框
        <teleport to="body">
            <div class="box" v-show="show">
                <button @click="show = false">关闭</button>
            </div>
        </teleport>

异步加载组件

导入API
import { defineAsyncComponent } from 'vue'
// 异步导入组件
let Two = defineAsyncComponent(() => import('../components/Two.vue'))
//注意: 如果异步组件内部是promise异步对象,就必须用异步导入的方式
let Three = defineAsyncComponent(() => import('../components/Three.vue'))
例如:
    return new Promise((res, req) => {
            setTimeout(() => {
                res({
                    foods
                })
            }, 2000);
        })
// 异步组件显示loading
<Suspense>
            <!-- default插槽展示异步组件 -->
            <template #default>
                <Three></Three>
            </template>
            <!-- fallback插槽展示组件前的展示信息 -->
            <template #fallback>
                <div>Loading...</div>
            </template>
        </Suspense>

动画组件

/* 进入时和离开时 */
.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.5s ease;
}
/* 进入前和离开后 */
.fade-enter-from,
.fade-leave-to {
    opacity: 0;
}

组件传值

1.mitt库

v2写法

// 安装
 npm install --save mitt
// main.js导入Mitt 
import mitt from "mitt"
let app = createApp(App)
    //挂载事务总线为全局属性
    // config.globalProperties返回的就是vue的原型对象
app.config.globalProperties.$bus = new mitt()
app.mount('#app')
组件发布消息
  mounted() {
        // 发布消息
        this.$bus.on('update-plane-name', (e) => {
            this.planName = e
        })
        // 发布消息
        this.$bus.on('update-plane-price', (e) => {
            this.planPrice = e
        })
    },
另一个组件订阅消息
  methods: {
        updatePlanName() {
            // 触发事件
            this.$bus.emit('update-plane-name', '空客')
        },
        updatePlanPrice() {
            // 触发事件
            this.$bus.emit('update-plane-price', 99)
        }
    },

v3写法

第一步  下载和引入
 npm install --save mitt
// main.js导入Mitt 
import mitt from "mitt"
let app = createApp(App)
    //挂载事务总线为全局属性
    // config.globalProperties返回的就是vue的原型对象
app.config.globalProperties.$bus = new mitt()
app.mount('#app')
第二步 当前组件发布消息
// 引入getCurrentInstance 当前组件实例
import {  getCurrentInstance } from 'vue'
//发布消息
   setup() {
        let dogName = ref('旺财')
        let dogSex = ref('妹妹')
        // 获取当前组件实例自身的this。不等同于This
        let $this = getCurrentInstance()
        console.log($this.appContext.config.globalProperties.$bus);

        $this.appContext.config.globalProperties.$bus.on('update-dog-name', (e) => {
            dogName.value = e
        })
        $this.appContext.config.globalProperties.$bus.on('update-dog-sex', (e) => {
            dogSex.value = e
        })
        return {
            dogName, dogSex
        }
    }
第三步 订阅组件触发事件
import { getCurrentInstance } from 'vue'
  setup() {
        let $this = getCurrentInstance()
        let updateDogName = () => {
            $this.appContext.config.globalProperties.$bus.emit('update-dog-name', '小强')
        }
        let updateDogSex = () => {
            $this.appContext.config.globalProperties.$bus.emit('update-dog-sex', '哈士奇')
        }
        return {
            updateDogName, updateDogSex
        }
    }

2.proviede和inject

1.传属性
// 第一个参数是key,第二个是value
    // 注意:name是传过去整个对象,后代修改值也会影响父级,
    // 注意:name.value传过去的是name的值,不具备响应式
    // provide('name', name)
    provide('name', name.value)
    provide('age', age)
// 接属性
    // 注意:如果父级传过来的值是整个ref对象,那么自己修改值会影响到父级
    //注意:如果父级穿过来的只是个值,与需要用ref(inject('name'))包裹才具备响应式,但是修改不会影响父级
    // let myName = inject('name')
      let myName = ref(inject('name'))
      let myAge = inject('age')
// 2.传方法
    let updateCarName = (val) => {
      car.name = val
    }
    let updateCarPrice = (val) => {
      car.price = val
    }
    provide('updateCarName', updateCarName)
    provide('updateCarPrice', updateCarPrice)
// 接方法
    let updateCarName = inject('updateCarName')
    let updateCarPrice = inject('updateCarPrice')

3.v-molde

//  v-model双向绑定时必须指定属性的名称 
    <Five v-model:bg="bg" v-model:xj="xj" v-model:lb="lb" />
// 必须接一下
 props: ['bg', 'lb', 'xj'],
        // setup两个参数
        // console.log(props);
        // console.log(context); emit回传事件
2.组件接收值,注意要解构出emit回传事件
    setup(props, { emit }) {
        let bg = ref(props.bg)
        let lb = ref(props.lb)
        let xj = ref(props.xj)
        console.log(props);
        watch(bg, (val) => {
            emit('update:bg', val)
        })
        watch(xj, (val) => {
            emit('update:xj', val)
        })
        watch(lb, (val) => {
            emit('update:lb', val)
        })
        return {
            bg, lb, xj
        }
    }

4.props

export default {

    name: 'Two',
    // V3的props接收到的数据可以在模板中直接修改,但是是只改自己,不改父组件
    //但是在methods里面是不行的,还得备份中转
    props: ['name', 'age'],
    // $attrs获取到的属性也是只读的,要中转  $attrs['car-name']
    setup(props, context) {
        // console.log(props);
        // console.log(context);
        // console.log(context.attrs);
        //  两个参数,props组件接收到的数据
        //context 里面包含attrs 是props未接收的属性     emit是事件回传   slots是插槽
        let myName = toRef(props, 'name') //ref(props.name)
        let myAge = toRef(props, 'age')
        let mySex = toRef(context.attrs, 'sex')

        let updateName = () => {
            myName = '刘翔'
            console.log(myName);
            // context.emit('change-name', myName)
            context.emit('update:name', myName)
        }
        let updateAge = () => {
            myAge = 40
            console.log(myAge);
            // context.emit('change-age', myAge)
            context.emit('update:age', myAge)
        }
        let updateSex = () => {
            mySex = '女'
            console.log(mySex);
            // context.emit('change-sex', mySex)
            context.emit('update:sex', mySex)
        }
        return {
            myName, myAge, mySex, updateName, updateAge, updateSex
        }
    }
}

响应式的区别

vue2的响应式原理

        // 代理对象 用obj2代理obj1   给对象身上的每一个属性都匹配getter和setter
        //   然后通过修改obj2来改变obj1
         let obj2 = {}
        Object.keys(obj).forEach(k => {
                Object.defineProperty(obj2, k, {
                    get() {
                        return obj[k]
                    },
                    set(val) {
                        obj[k] = val
                        show()
                    }
                })
            }) 

vue3的响应式原理

        //  和vue2的代理方式不同,直接创建一个Proxy()代理对象,
        // 通过Proxy劫持到源数据 在代理中修改数据反射给源数据
    let obj2 = new Proxy(obj, {
            //获取属性
            // target源对象 propertyName属性名
            get(target, propertyName) {
                // return target[propertyName]
                // 或者反射属性的值Reflect
                return Reflect.get(target, propertyName)
            },
            // 设置属性
            set(target, propertyName, value) {
                // target[propertyName] = value
                // 反射
                Reflect.set(target, propertyName, value)
                show()
            },
            // 删除属性 
            deleteProperty() {
                // delete target[propertyName]
                // 反射删除
                Reflect.deleteProperty(target, propertyName)
                show()
            }
        })
        //   显示信息
    function show() {
        document.querySelector('#name').innerHTML = obj.name
        document.querySelector('#age').innerHTML = obj.age
        document.querySelector('#sex').innerHTML = obj.sex
            // 将对象转为字符串
        document.querySelector('#show').innerHTML = JSON.stringify(obj)
    }
    show()
    document.querySelector('#setName').onclick = function() {
        obj.name += 1
        show()
    }
    document.querySelector('#setAge').onclick = function() {
        obj.age++
            show()
    }
    document.querySelector('#addSex').onclick = function() {
        obj2.sex = '男'
        show()
        console.log(obj);
        console.log(obj2);
    }
    document.querySelector('#delAge').onclick = function() {
        delete obj.age
        show()
        console.log(obj);
        console.log(obj2);
    }

购物车案例

<template>
    <div class="shop">
        <table v-cloak>
            <!-- 表头 -->
            <thead>
                <tr>
                    <th>
                        <input type="checkbox" v-model="isCkAll" />全选
                    </th>
                    <th style="width: 60px; height: 50px;">名称</th>
                    <th>图片</th>
                    <th style="width: 60px;">单价</th>
                    <th style="width: 100px;">数量</th>
                    <th style="width: 60px;">小计</th>
                    <th style="width: 60px;">操作</th>
                </tr>
            </thead>
            <!-- 主体 -->
            <tbody v-if="this.goods.length > 0">
                <tr v-for="(g, index) in goods" :key="g.id">
                    <td>
                        <input type="checkbox" v-model="g.isCk" />
                    </td>
                    <td>{{ g.name }}</td>
                    <td>
                        <img :src="g.pic" />
                    </td>
                    <td>{{ g.price }}</td>
                    <td>
                        <button @click="g.count--" :disabled="g.count === 1">-</button>
                        <input type="text" class="count" v-model.number="g.count" readonly />
                        <button @click="g.count++">+</button>
                    </td>
                    <td>{{ g.price * g.count }}</td>
                    <td>
                        <a href="javascript:;" @click="del(index)">删除</a>
                    </td>
                </tr>
            </tbody>
            <tbody v-else>
                <tr>
                    <td colspan="7">
                        <img
                            class="del"
                            src="http://www.ykyao.com/postsystem/docroot/images/kindeditor/image/20160406/2016040615404366627.png"
                        />
                    </td>
                </tr>
            </tbody>
            <!-- 尾部 -->
            <tfoot>
                <td colspan="7" style="text-align: right;">
                    <span>总价:{{ totalPrice }}</span>
                    <span></span>
                </td>
            </tfoot>
        </table>
    </div>
</template>
<script>
import { reactive, computed, watchEffect } from '@vue/reactivity'
export default {
    name: 'Shopping',
    setup() {
        // 定义数据
        //#region   商品列表
        const goods = reactive([{
            id: 1,
            name: '手表',
            pic: 'https://img13.360buyimg.com/n7/jfs/t1/90088/17/20462/75026/61d1b2e7Efd207486/4723905806de1f2c.jpg',
            price: 2199,
            count: 3,
            isCk: false
        }, {
            id: 2,
            name: '手机',
            pic: 'https://img13.360buyimg.com/n7/jfs/t1/155183/24/7208/46178/5fbe0446E88417894/63cd6bffac98185d.jpg',
            price: 3999,
            count: 1,
            isCk: true
        }, {
            id: 3,
            name: '电视',
            pic: 'https://img10.360buyimg.com/n7/jfs/t1/200000/38/15689/51980/6182619eE5044433f/22a173da97a3d92e.jpg',
            price: 3499,
            count: 1,
            isCk: true
        }, {
            id: 4,
            name: '冰箱',
            pic: 'https://img10.360buyimg.com/n7/jfs/t1/149908/15/2773/53205/5f0ad38bEeeedb207/aefa4ba990cbf08d.jpg',
            price: 2999,
            count: 1,
            isCk: false
        }])
        //#region   
        // 定义方法
        // 删除方法  
        const del = function (index) {
            goods.splice(index, 1)
        }
        // 计算总价   计算属性
        const totalPrice = computed(() => {
            return goods.filter(r => r.isCk).map(r => r.price * r.count).reduce((a, b) => {
                return a + b
            }, 0)
        })
        // 判断全选
        const isCkAll = computed({
            get() {
                return goods.every(r => r.isCk)
            },
            set(val) {
                goods.forEach(r => r.isCk = val)
            }
        })

        return {
            goods, del, totalPrice, isCkAll
        }


    }
}
</script>
<style scoped >
table {
    border-collapse: collapse;
    text-align: center;
}
th,
td {
    border: 1px solid #ccc;
}
img {
    width: 50px;
}
input {
    width: 30px;
    text-align: center;
}
</style>

钩子

注意 :setup在钩子之前执行

<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {
    setup() {
        onBeforeMount(() => {
            console.log('onBeforeMount');
        })
        onMounted(() => {
            console.log('onMounted');
        })
        onBeforeUpdate(() => {
            console.log('onBeforeUpdate');
        })
        onUpdated(() => {
            alert('修改成功')
            console.log("unUpdated");
        })
        onBeforeUnmount(() => {
            console.log('onBeforeUnmount');
        }),
            onUnmounted(() => {
                console.log('onUnmounted');
            })
        let count = ref(1)
        return {
            count, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted
        }
    }
}
</script>

自定义hook

定义hook

import { ref, computed } from 'vue'
export default function() {
    // 汽车相关
    const carName = ref("奔驰");
    const carPrice = ref(200);
    const updateCarName = function() {
        carName.value = "宝马";
    };
    const updateCarPrice = function() {
        carPrice.value = 600;
    };
    const carZkPrice = computed(() => {
        return (carPrice.value * 0.8).toFixed(2);
    });
    return {
        carName,
        carPrice,
        updateCarName,
        updateCarPrice,
        carZkPrice,
    }
}

使用hook

<script>
// 导入的hook是函数
import useCar from "@/hook/useCar";
import usePhone from "@/hook/usePhone";
import usePlane from "@/hook/usePlane";

export default {
  name: "One",
  setup() {
    return {
        // 展开运算符
      ...useCar(),
      ...usePhone(),
      ...usePlane()

    };
  },
};
</script>

API

toRef, toRefs, readonly, shallowRef, shallowReadonly

<script>
import { toRef, toRefs, reactive, readonly, shallowRef, shallowReadonly } from "vue";
export default {
  name: "Ref",
  setup() {
    let stu = reactive({
      no: 1001,
      name: "李欢",
      sex: '男',
      age: 29,
    });
    // readonly定义只读,不管嵌套多少都只读
    let car = readonly({
      name: '宾利',
      price: 100,
    })
    // 只读,无法增删改
    // car.name = "劳斯莱斯"
    // console.log(car);

    // shallowRef 浅响应式
    const stu2 = shallowRef({
      name: '张三',
      age: 99
    })
    const updateStu2 = function () {
      // 这样修改是有效的
      // stu2.value = {
      //   name: '李四',
      //   age: '80'
      // }
      // 这样修改是无效的 改不了
      stu2.value.name = "李四"
    }
    // shallowReactive 定义浅响应式的reactive对象  只对对应的第一层有响应式,深层次的嵌套不跟踪
    // shallowReadonly 定义浅层次的只读对象,只有第一层是只读的,后面的不只读可以修改但不具备响应式

    return {
      ...toRefs(car),
      // toRef方法用于将一个reactive里的成员转换为ref对象,保证响应式
      no: toRef(stu, 'no'),
      // 使用展开运算符配合toRefs将对象包裹,使模板更简洁
      ...toRefs(stu),
      stu2, updateStu2
    };
  },
};
</script>

toRaw, markRaw

<script>
import { reactive, toRaw, markRaw } from "vue"
export default {
  name: 'One',
  setup(props) {
    let obj = {
      name: '张三',
      age: 20
    }
    const stu1 = reactive(obj)
    // toRaw将一个代理对象转换为普通对象  这个普通对象就是被代理的对象
    //应用场景,当前组件接收到一个响应式对象时,将他转为非响应式
    const stu2 = toRaw(stu1)
    console.log(obj, stu1, stu2);

    const stu3 = JSON.parse(JSON.stringify(stu1))  //深度克隆
    console.log(obj === stu2);  //true
    console.log(obj === stu3);   //false

    // markRaw()包装后的对象再也不能称为响应式对象
    let obj2 = {
      name: '李四',
      age: 20
    }
    markRaw(obj2) //失去响应式了,即使加了reactive也不可以了


    return {
      stu1, stu2
    }
  }
}
</script>

H5适配

1.依赖安装

转rem,兼容浏览器css,自动加前缀,flexible适配不同设备视口

npm i postcss-loader postcss-pxtorem -D 
npm i autoprefixer@9.8.6
npm i lib-flexible -S

2.配置postcss

根目录新建postcss.config.js

module.exports = {
    "plugins": {
        "autoprefixer": {
            // 兼容哪些浏览器前缀
            overrideBrowserslist: [
                "Android 4.1",
                "iOS 7.1",
                "Chrome > 31",
                "ff > 31",
                "ie >= 11",
                'last 10 versions', // 所有主流浏览器最近2个版本
            ]
        },
        "postcss-pxtorem": {
            rootValue: 37.5, // 值为设计稿的宽度
            uniPrecision: 6,//几位小数
            replace: true, //rem替换px
            mediaQuery: true, //兼容媒体查询
            minPixeValue: 1, //设置要替换的最小像素值
            exclude: /(node_module)/,  //忽略转换文件
            // selectorBlackList: ['.van'], //要忽略并保留为px的选择器
            propList: ['*'],
        }
    }

}

3.main.js引入flexible

import 'lib-flexible'
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值