路由
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'