十六、vue3高级语法补充
1、自定义指令
<template>
<input type="text" v-focus>
</template>
<script>
export default {
directives: {
focus: {
mounted(el, bindings, vnode, preVnode) {
el.focus()
}
}
}
}
</script>
// main.js
import {createApp} from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive("focus", {
mounted(el, bindings, vnode, preVnode) {
el.focus()
}
})
app.mount('#app')
<template>
<input type="text" v-focus>
</template>
<script>
export default {}
</script>
2、指令的生命周期
vue3 |
vue2 |
生命周期 |
created |
新的 |
在绑定元素attribute或事件监听器被应用之前调用 |
beforeMount |
bind |
当指令第一次绑定到元素并且在挂载父组件之前调用 |
mounted |
inserted |
在绑定元素的父组件被挂载后调用 |
beforeUpdate |
新的 |
在更新包含组件的VNode之前调用 |
updated |
update(移除)、componentUpdated |
在包含组件的VNode及其子组件的VNode更新后调用 |
beforeUnmount |
新的 |
在卸载绑定元素的父组件之前调用 |
unmounted |
unbind |
当指令与元素解除绑定且父组件已卸载时,只调用一次 |
3、指令的修饰符和参数
<template>
<!-- 1、dom或vDom卸载的时候,指令也会卸载 -->
<!-- 2、v-指令.修饰符="参数" -->
<button v-if="counter<2" v-person.gender="'黄婷婷'" @click="clickHandler">计数器{{counter}}</button>
</template>
<script>
import {ref} from "vue"
export default {
directives: {
person: {
created(el, bindings, vnode, preVnode) {
console.log("生命周期:created")
console.log(bindings.value)// 参数:黄婷婷
console.log(bindings.modifiers)// 修饰符:{gender: true}
},
beforeMount() {
console.log("生命周期:beforeMount")
},
mounted() {
console.log("生命周期:mounted")
},
beforeUpdate() {
console.log("生命周期:beforeUpdate")
},
updated() {
console.log("生命周期:updated")
},
beforeUnmount() {
console.log("生命周期:beforeUnmount")
},
unmounted() {
console.log("生命周期:unmounted")
}
}
},
setup() {
const counter = ref(0)
const clickHandler = () => {
counter.value++
}
return {counter, clickHandler}
}
}
</script>
4、案例
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import registerDirectives from "./directives"
const app = createApp(App)
registerDirectives(app)
app.mount('#app')
<!-- src/App.vue -->
<template>
<p v-format-time="'YYYY/MM/DD'">{{timestamp}}</p>
</template>
<script>
import {ref} from "vue"
export default {
setup() {
const timestamp = ref(1650422781756)
return {
timestamp
}
},
mounted() {
// 指令比dom或vDom先挂载
console.log("组件")
}
}
</script>
// src/directives/index.js
import formatTime from "./format-time"
export default function (app) {
formatTime(app)
}
// src/directives/format-time.js
import dayjs from "dayjs"
export default function (app) {
app.directive("format-time", {
created(el, bindings) {
bindings.formatString = bindings.value || "YYYY-MM-DD HH:mm:ss"
},
mounted(el, bindings) {
console.log("指令")
const textContent = parseInt(el.textContent)
// npm i -S dayjs
el.textContent = dayjs(textContent).format(bindings.formatString)
}
})
}
5、teleport
* 某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到Vue app之外的其他位置
* teleport的两个属性
- to:指定将其中的内容移动到的目标元素,可以使用选择器
- disabled:是否禁用teleport的功能
<template>
<div class="app">黄婷婷</div>
<div class="body">孟美岐</div>
<!-- 不指定to属性,则直接不显示。多个相同的to属性,则做合并 -->
<teleport to=".body">
<div>姜贞羽</div>
</teleport>
<teleport to=".body">
<div>鞠婧祎</div>
</teleport>
</template>
6、Vue插件
* 通常我们向Vue全局添加一些功能时,会采用插件的模式,它有两种编写方式
- 对象类型:一个对象,但是必须包含一个install的函数,该函数会在安装插件时执行
- 函数类型:一个function,这个函数会在安装插件时自动执行
* 插件可以完成的功能没有限制,比如下面的几种都是可以的
- 添加全局方法或者property,通过把它们添加到config.globalProperties上实现
- 添加全局资源:指令/过滤器/过渡等
- 通过全局mixin来添加一些组件选项
- 一个库,提供自己的api,同时提供上面提到的一个或多个功能
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import pluginObject from "./plugins/plugins_object"
import pluginFunction from "./plugins/plugins_function"
const app = createApp(App)
app.use(pluginObject)
app.use(pluginFunction)
app.mount('#app')
// src/plugins/plugins_object.js
export default {
install(app) {
// 全局属性命名方式一般以$开头
app.config.globalProperties.$name = "黄婷婷"
}
}
<!-- src/components/First.vue -->
<template>
<div></div>
</template>
<script>
export default {
mounted() {
console.log(this.$name)
}
}
</script>
// src/plugins/plugins_function.js
export default function (app) {
app.config.globalProperties.$age = 18
}
<!-- src/components/Second.vue -->
<template>
<div></div>
</template>
<script>
import {getCurrentInstance} from "vue"
export default {
setup() {
const instance = getCurrentInstance();
const $age = instance.appContext.config.globalProperties.$age;
console.log($age)
return {}
}
}
</script>
十七、vue3源码学习
1、虚拟dom的优势
* 将真实元素抽象成vnode(js对象),操作更加方便
* 避免直接操作dom,减少浏览器回流,提高性能
* 不同的渲染器,可在对应的平台(ios、android)渲染
2、虚拟dom的渲染过程
* template -> render() -> vnode -> patch(n1,n2)(diff算法) -> dom(渲染)
3、vue源码三大核心系统
* compiler模块:编译模板系统
* runtime模块:也可以称之为renderer模块,真正渲染的模块
* reactivity模块:响应式系统
4、实现简单的vue
* 渲染系统模块
- h函数,用于返回一个vnode对象
- mount函数,用于将vnode挂载到dom上
- patch函数,用于对两个vnode进行对比,决定如何处理新的vnode
* 可响应式系统模块
* 应用程序入口模块
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue3</title>
</head>
<body>
<div id="app"></div>
<script src="./renderer.js"></script>
<script src="./reactive.js"></script>
<script src="./index.js"></script>
<script>
const App = {
data: reactive({
counter: 0
}),
render() {
return h("div", null, [
h("h2", null, `当前计数:${this.data.counter}`),
h("button", {
onClick: () => {
this.data.counter++
}
}, "+1")
])
}
}
const app = createApp(App)
app.mount("#app")
</script>
</body>
</html>
// 一、渲染系统模块
// a、h函数:创建vnode
const h = (tag, props, children) => {
return {
tag,
props,
children
}
}
// b、mount函数:挂载dom
const mount = (vnode, container) => {
// 1、创建真实元素
const el = vnode.el = document.createElement(vnode.tag)
// 2、处理props
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key]
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), value)
} else {
el.setAttribute(key, value)
}
}
}
// 3、处理children
if (vnode.children) {
if (typeof vnode.children === "string") {
el.textContent = vnode.children
} else {
vnode.children.forEach(item => {
mount(item, el)
})
}
}
// 4、递归添加子节点
container.appendChild(el)
}
// c、patch函数:diff算法
const patch = (n1, n2) => {
if (n1.tag !== n2.tag) {
const n1ElParent = n1.el.parentElement
n1ElParent.removeChild(n1.el)
mount(n2, n1ElParent)
} else {
// 1、取出n1的el,并在n2中进行保存
const el = n2.el = n1.el
// 2、处理props
const oldProps = n1.props || {}
const newProps = n2.props || {}
// 2.1、获取所有的newProps添加到el
for (const key in newProps) {
const oldValue = oldProps[key]
const newValue = newProps[key]
if (newValue !== oldValue) {
if (key.startsWith("on")) {
el.addEventListener(key.slice(2).toLowerCase(), newValue)
} else {
el.setAttribute(key, newValue)
}
}
}
// 2.2、删除旧的props
for (const key in oldProps) {
if (key.startsWith("on")) {
const value = oldProps[key]
el.removeEventListener(key.slice(2).toLowerCase(), value)
}
if (!(key in newProps)) {
el.removeAttribute(key)
}
}
// 3、处理children
const oldChildren = n1.children || []
const newChildren = n2.children || []
if (typeof newChildren === "string") {
if (typeof oldChildren === "string") {
if (newChildren !== oldChildren) {
el.textContent = newChildren
}
} else {
el.innerHTML = newChildren
}
} else {
if (typeof oldChildren === "string") {
el.innerHTML = ""
newChildren.forEach(item => {
mount(item, el)
})
} else {
/**
* 1、为什么使用key性能更高?
* - key相等的做patch,vnode可以尽量做move,而减少mount和unmount
*/
const commonLength = Math.min(oldChildren.length, newChildren.length)
// 3.1、oldChildren.length === newChildren.length:递归patch(n1, n2)
for (let i = 0; i < commonLength; i++) {
patch(oldChildren[i], newChildren[i])
}
// 3.2、oldChildren.length < newChildren.length:挂载mount(vnode, container)
if (oldChildren.length < newChildren.length) {
newChildren.slice(oldChildren.length).forEach(item => {
mount(item, el)
})
}
// 3.3、oldChildren.length > newChildren.length:卸载
if (oldChildren.length > newChildren.length) {
oldChildren.slice(newChildren.length).forEach(item => {
el.removeChild(item.el)
})
}
}
}
}
}
class Dep {
constructor() {
this.subscribers = new Set()
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect)
}
}
notify() {
this.subscribers.forEach(effect => {
effect()
})
}
}
let activeEffect = null
function watchEffect(effect) {
activeEffect = effect
effect()
activeEffect = null
}
const targetMap = new WeakMap()
function getDep(target, key) {
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Dep()
depsMap.set(key, dep)
}
return dep
}
function reactive(raw) {
// vue2
/*Object.keys(raw).forEach(key => {
const dep = getDep(raw, key)
let value = raw[key]
Object.defineProperty(raw, key, {
get() {
dep.depend()
return value
},
set(newValue) {
if (value !== newValue) {
value = newValue
dep.notify()
}
}
})
})*/
// vue3
return new Proxy(raw, {
get(target, key) {
const dep = getDep(target, key)
dep.depend()
return target[key]
},
set(target, key, newValue) {
const dep = getDep(target, key)
target[key] = newValue
dep.notify()
}
})
}
function createApp(rootComponent) {
return {
mount(selector) {
const container = document.querySelector(selector)
let isMounted = false
let oldVNode = null
watchEffect(function () {
if (!isMounted) {
oldVNode = rootComponent.render()
mount(oldVNode, container)
isMounted = true
} else {
const newVNode = rootComponent.render()
patch(oldVNode, newVNode)
oldVNode = newVNode
}
})
}
}
}
5、源码阅读
* vnode和组件instance的区别?
- vnode由render函数的参数h函数产生。组件instance用于保存组件的各种状态
* 有状态组件和无状态组件?
- 有状态组件(对象):const app={data,method,setup}
- 无状态组件(函数):function Component(){}
* 编译器原理?
- C语言 -> parser(解析器) -> C语言AST -> Python语言AST -> 生成代码 -> Python语言
- template -> parser -> template ast -> js ast -> generate -> render
* 性能优化?
- 静态节点作用域提升:生成的render函数中,对于不会改变的静态节点,进行作用域的提升
- block tree:只对动态节点(dynamicChildren)做patch操作
* template中数据的使用顺序?
- setup -> data -> props -> ctx(methods/computed)
(packages/runtime-core/src/componentPublicInstance.ts:277)
* webstorm -> favorites -> bookmarks
* 右击源码窗口左边栏 -> set bookmark -> edit bookmark description
函数 |
vue.global.js |
createApp |
9476 |
createAppAPI |
6100 |
mount |
4301 |
render |
4310 |
patch |
6075 |
processComponent |
4994 |
mountComponent |
5374 |
setupComponent |
5398 |
setupRenderEffect |
5414 |
patch |
5490 |
processElement |
4991 |
mountElement |
5071 |
mountChildren |
5089 |
patch |
5167 |
hostCreateElement |
5082 |
hostInsert |
5129 |
函数 |
vue.global.js |
setupComponent |
5398 |
setupStatefulComponent |
7679 |
handleSetupResult |
7739 |
finishComponentSetup |
7771 |
compile |
7792 |
applyOptions |
7812 |
函数 |
vue.global.js |
compile |
7792 |
registerRuntimeCompiler |
7780 |
compileToFunction |
13765 |
compile$1 |
13791 |
baseCompile |
13746 |
baseParse |
13302 |
transform |
13304 |
generate |
13313 |
函数 |
vue.global.js |
setupRenderEffect |
5414 |
renderComponentRoot |
5472 |
normalizeVNode |
2090 |
openBlock |
6473 |
createBlock |
6511 |
patchBlockChildren |
5344 |
函数 |
vue.global.js |
applyOptions |
7812 |
beforeCreate |
6963 |
created |
7138 |
onBeforeMount |
7141 |
invokeArrayFns |
5462 |
queuePostRenderEffect |
5498 |
flushPostFlushCbs |
6077 |
unmountComponent |
6009 |
十八、VueRouter
1、URL的hash
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
</head>
<body>
<a href="#/home">home</a>
<a href="#/about">about</a>
<div class="router-view"></div>
<script>
const routerView = document.querySelector(".router-view")
addEventListener("hashchange", () => {
switch (location.hash) {
case "#/home":
routerView.innerHTML = "home"
break;
case "#/about":
routerView.innerHTML = "about"
break;
default:
routerView.innerHTML = "default"
}
})
</script>
</body>
</html>
2、HTML5的History
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-router</title>
</head>
<body>
<a href="/home" onclick="routerHandler(event,this)">home</a>
<a href="/about" onclick="routerHandler(event,this)">about</a>
<div class="router-view"></div>
<script>
const routerView = document.querySelector(".router-view")
const changeContent = (pathname) => {
switch (pathname) {
case "/home":
routerView.innerHTML = "home"
break;
case "/about":
routerView.innerHTML = "about"
break;
default:
routerView.innerHTML = "default"
}
}
/**
* 1、history接口是HTML5新增的,它有六种模式改变URL而不刷新页面
* - replaceState:替换原来的路径
* - pushState:使用新的路径
* - popState:路径的回退
* - go:向前或向后改变路径
* - forward:向前改变路径
* - back:向后改变路径
*/
const routerHandler = (ev, el) => {
ev.preventDefault()
const href = el.getAttribute("href");
history.pushState({}, "", href)
changeContent(location.pathname)
}
addEventListener("popstate", changeContent)
</script>
</body>
</html>
3、路由的基本使用
npm i -S vue-router
// src/router/index.js
/**
* createWebHashHistory:hash模式
* createWebHistory:history模式
*/
import {createRouter, createWebHashHistory, createWebHistory} from "vue-router"
const routes = [
/**
* 1、默认路径:"/"
* 2、redirect:重定向
*/
{path: "/", redirect: "/first"},
/**
* 3、路由懒加载
* 4、魔法注释
*/
{path: "/first", component: () => import(/* webpackChunkName: "first-chunk" */"../components/First")},
/**
* 5、其他属性:
* - name
* - meta
* 8、路由嵌套:以"/"开头,则不与父路径拼接
*/
{
name: "second",
path: "/second",
component: () => import("../components/Second"),
meta: {},
children: [
{path: "", redirect: "/second/one"},
{path: "one", component: () => import("../components/SecondOne")}
]
},
/**
* 6、动态路由匹配(可匹配多个参数)
*/
{path: "/third/:person", component: () => import("../components/Third")},
/**
* 7、任意路径匹配
*/
{path: "/:pathMatch(.*)*", component: () => import("../components/Fourth")},
]
const router = createRouter({
routes,
history: createWebHashHistory()
})
export default router
// src/permission.js
import router from "./router"
/**
* 1、参数
* - to:Route对象,即将跳转到的Route对象
* - from:Route对象
* - next:有但不用
* 2、返回值
* - false:不进行导航
* - undefined或者不写返回值:进行默认导航
* - 字符串:路径,跳转到对应的路径中
* - 对象:类似于router.push({path:"/login",query:{}})
* 3、完整的导航解析流程
* - 导航被触发
* - 在失活的组件里调用beforeRouteLeave守卫
* - 调用全局的beforeEach守卫
* - 在重用的组件里调用beforeRouteUpdate守卫(/third/:person)
* - 在路由配置里调用beforeEnter({path,component,beforeEnter(to,from){return}})
* - 解析异步路由组件
* - 在被激活的组件里调用beforeRouteEnter
* - 调用全局的beforeResolve守卫
* - 导航被确认
* - 调用全局的afterEach钩子
* - 触发DOM更新
* - 调用beforeRouteEnter守卫中传给next的回调函数,创建好的组件实例
* 会作为回调函数的参数传入({beforeRouteEnter(to,from,next){next(vm=>{})}})
*/
router.beforeEach((to, from) => {
})
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import router from "./router";
import "animate.css"
const app = createApp(App)
app.use(router)
app.mount('#app')
<!-- src/App.vue -->
<template>
<div>
<!--
1、router-link
- to:可以是string或者object
- replace:调用router.replace(),而不是router.push()
- active-class:默认router-link-active,路由开头匹配
- exact-active-class:默认router-link-exact-active,路由精准匹配
-->
<button @click="routerGo">返回</button>
<br>
<button @click="routerPush">first页面</button>
<br>
<!--
2、router-link插槽
- href:跳转的链接
- route:对象
- navigate:导航函数
- isActive:是否当前处于活跃的状态
- isExactActive:是否当前处于精确的活跃状态
-->
<button @click="routerAdd">添加路由</button>
<br>
<router-link to="/second" v-slot="props" custom>
<button @click="props.navigate">second页面</button>
</router-link>
<br>
<router-link to="/third/htt">third页面</router-link>
<br>
<router-link to="/fourth/mmq">fourth页面</router-link>
<!--
3、router-view插槽
- Component:要渲染的组件
- route:解析出的标准化路由对象
-->
<p style="overflow: hidden">
<router-view v-slot="props">
<transition name="fade-out-in" mode="out-in">
<keep-alive>
<component :is="props.Component"></component>
</keep-alive>
</transition>
</router-view>
</p>
</div>
</template>
<script>
import {useRouter} from "vue-router"
export default {
setup() {
const router = useRouter()
const routerPush = () => {
// router.push("/first")
// router.replace("/first")
router.push({
path: "/first",
query: {
name: "黄婷婷",
age: 18
}
})
}
const routerGo = () => {
// router.go(1)
// router.forward()
// router.go(-1)
router.back()
}
const routerAdd = () => {
/*
// 1、添加顶级路由对象
router.addRoute({
name: "second",
path: "/second",
component: () => import("./components/Second")
})*/
// 2、添加二级路由对象
router.addRoute("second", {
path: "two",
component: () => import("./components/SecondTwo"),
children: []
})
/**
* 3、动态删除路由
* - 添加一个name相同的路由
* - 通过removeRoute方法,传入路由的名称
* - 通过addRoute方法的返回值回调
* 4、路由的其他方法补充
* - router.hasRoute():检查路由是否存在
* - router.getRoutes():获取一个包含所有路由记录的数组
*/
}
return {
routerPush,
routerGo,
routerAdd
}
}
}
</script>
<style scoped>
.fade-out-in-enter-active {
animation: fadeInLeft 0.3s ease;
}
.fade-out-in-leave-active {
animation: fadeInRight 0.3s ease reverse;
}
</style>
<!-- src/components/First.vue -->
<template>
<div>获取route.query</div>
</template>
<script>
import {useRoute} from "vue-router"
export default {
setup() {
const route = useRoute()
console.log(route.query)// {name: '黄婷婷', age: '18'}
return {}
}
}
</script>
<!-- src/components/Second.vue -->
<template>
<div>
<div>嵌套路由</div>
<p>
<router-view></router-view>
</p>
<router-link to="/second/one">second-one</router-link>
<br>
<router-link to="/second/two">second-two</router-link>
</div>
</template>
<script>
export default {
setup() {
return {}
}
}
</script>
<!-- src/components/Third.vue -->
<template>
<div>动态路由匹配</div>
</template>
<script>
import {useRoute} from "vue-router"
export default {
setup() {
const route = useRoute()
console.log(route.params.person)// htt
return {}
}
}
</script>
<!-- src/components/Fourth.vue -->
<template>
<div>任意路径匹配</div>
</template>
<script>
import {useRoute} from "vue-router"
export default {
setup() {
const route = useRoute()
// "/:pathMatch(.*)":不加*为字符串,加*为字符串数组
console.log(route.params.pathMatch)
return {}
}
}
</script>
十九、vuex
* 手动安装
- https://github.com/vuejs/devtools/archive/refs/tags/v6.1.4.zip
- yarn install
- yarn run build
- 谷歌浏览器,加载已解压的扩展程序
* chrome商店安装
2、vuex的基本使用
npm i -S vuex
// src/store/index.js
import {createStore} from "vuex"
import user from "./user"
const store = createStore({
state() {
return {
counter: 0,
age: 18,
children: ["姜贞羽"]
}
},
getters: {
name() {
return "黄婷婷"
},
person(state, getters) {
return function (address) {
return {
name: getters.name,
age: state.age,
address
}
}
}
},
mutations: {
increment(state, payload) {
state.counter += payload.num
},
decrement(state, payload) {
state.counter -= payload.num
},
SET_AGE(state, payload) {
state.age = payload.age
},
SET_CHILDREN(state, payload) {
state.children.push(payload)
}
},
// 5.1、actions处理异步操作,mutations不可以处理异步操作
actions: {
/**
* 5.2、context的属性
* - commit
* - dispatch
* - getters
* - state
*/
setAge(context, payload) {
context.commit("SET_AGE", payload)
},
setChildren({commit}, payload) {
return new Promise((resolve) => {
setTimeout(() => {
commit("SET_CHILDREN", payload)
resolve("派发成功")
}, 1000)
})
}
},
modules: {
user
}
})
export default store
const userModule = {
namespaced: true,
state() {
return {
userInfo: {
username: "佟丽娅"
}
}
},
getters: {
/**
* 6.2、namespaced为true时
* - getters:会多两个参数rootState、rootGetters
* - actions:context会多两个属性rootState、rootGetters
*/
getUserInfo(state, getters, rootState, rootGetters) {
return state.userInfo
}
},
mutations: {
setUserInfo(state, payload) {
state.userInfo.username = payload
}
},
actions: {
asyncUserInfo(context, payload) {
context.commit("setUserInfo", payload)
// 6.3、调用根的mutations和actions
context.commit("increment", {num: 10}, {root: true})
context.dispatch("setAge", {age: 14}, {root: true})
}
}
}
export default userModule
// src/main.js
import {createApp} from 'vue'
import App from './App.vue'
import store from "./store";
const app = createApp(App)
app.use(store)
app.mount('#app')
// src/hooks/useMapper.js
import {useStore} from "vuex";
import {computed} from "vue";
export default function (mapper, mapFn) {
const store = useStore();
const storeStateFns = mapFn(mapper);
const storeState = {}
Object.keys(storeStateFns).forEach(fnKey => {
const fn = storeStateFns[fnKey].bind({$store: store})
storeState[fnKey] = computed(fn)
})
return storeState
}
<!-- src/App.vue -->
<template>
<div>
<div>{{$store.state.counter}}</div>
<div>{{sCounter}}</div>
<!-- 3、辅助函数的两种风格 -->
<!-- <button @click="$store.commit('increment',{num:1})">+1</button> -->
<button @click="increment({num:1})">+1</button>
<!-- <button @click="$store.commit({type:'decrement',num:1})">-1</button> -->
<button @click="decrement({num:1})">-1</button>
<div>{{sAge}}</div>
<p>
<first></first>
</p>
<p>
<second></second>
</p>
</div>
</template>
<script>
import useMapper from "./hooks/useMapper"
import First from "./components/First"
import Second from "./components/Second"
import {computed} from "vue"
import {useStore, mapState, mapMutations} from "vuex"
export default {
components: {First, Second},
/*
computed: {
// 1、mapState辅助函数
// a、数组写法
// ...mapState(["counter"])
// b、对象写法(起别名)
...mapState({
sCounter: state => state.counter
})
}*/
/*
methods: {
// 4、mapMutations辅助函数
// a、数组写法
...mapMutations(["increment"]),
// b、对象写法
...mapMutations({
decrement: "decrement"
})
},*/
setup() {
const store = useStore()
const sAge = computed(() => store.state.age)
const storeState = useMapper({
sCounter: state => state.counter
}, mapState)
const storeMutations = mapMutations(["increment", "decrement"])
return {
...storeState,
sAge,
...storeMutations
}
}
}
</script>
<!-- src/components/First.vue -->
<template>
<div>
<div>{{$store.getters.person("无锡")}}</div>
<div>{{sName}}</div>
<div>{{personInfo("无锡")}}</div>
<!-- <button @click="$store.dispatch('setAge', {age: 16})">变年轻</button> -->
<!-- <button @click="$store.dispatch({type: 'setAge', age: 16})">变年轻</button> -->
<button @click="setAge({age: 16})">变年轻</button>
<div>{{$store.state.children}}</div>
</div>
</template>
<script>
import {mapGetters, useStore, mapActions} from "vuex"
import {computed, onMounted} from "vue"
import useMapper from "../hooks/useMapper";
export default {
/*
computed: {
// 2、mapGetters辅助函数
// a、数组写法
...mapGetters(["name"]),
// b、对象写法
...mapGetters({
personInfo: "person"
})
},*/
/*
methods: {
// 5.3、mapActions辅助函数
// a、数组写法
// ...mapActions(["setAge"]),
// b、对象写法
...mapActions({
setAge: "setAge"
})
},*/
setup() {
const store = useStore()
const sName = computed(() => store.getters.name)
const stateGetters = useMapper({
personInfo: "person"
}, mapGetters)
const storeActions = mapActions(["setAge"])
onMounted(() => {
store.dispatch("setChildren", "张婧仪").then(res => {
console.log(res)
})
})
return {
sName,
...stateGetters,
...storeActions
}
}
}
</script>
<!-- src/components/Second.vue -->
<template>
<div>
<!-- <div>{{$store.state.user.userInfo}}</div> -->
<!-- 6.1、命名空间:namespace:false -->
<!--
<div>{{$store.getters["getUserInfo"]}}</div>
<button @click="$store.commit('setUserInfo','林志玲')">换林志玲</button>
<button @click="$store.dispatch('asyncUserInfo','孟美岐')">换孟美岐</button> -->
<!-- namespace:true -->
<!--
<div>{{$store.getters["user/getUserInfo"]}}</div>
<button @click="$store.commit('user/setUserInfo','林志玲')">换林志玲</button>
<button @click="$store.dispatch('user/asyncUserInfo','孟美岐')">换孟美岐</button> -->
<div>state:{{userInfo}}</div>
<div>getters:{{getUserInfo}}</div>
<button @click="setUserInfo('林志玲')">换林志玲</button>
<button @click="asyncUserInfo('孟美岐')">换孟美岐</button>
</div>
</template>
<script>
import {computed} from "vue"
import {mapState, mapGetters, mapMutations, mapActions, createNamespacedHelpers, useStore} from "vuex"
const {mapState: userState, mapGetters: userGetters, mapMutations: userMutations, mapActions: userActions} = createNamespacedHelpers("user")
export default {
// 6.4、辅助函数
/*
computed: {
// 方式一
// ...mapState({userInfo: state => state.user.userInfo}),
// ...mapGetters({getUserInfo: "user/getUserInfo"}),
// 方式二(参数2可以是对象,可改别名)
// ...mapState("user", ["userInfo"]),
// ...mapGetters("user", ["getUserInfo"])
// 方式三(参数2可以是对象,可改别名)
...userState(["userInfo"]),
...userGetters(["getUserInfo"])
},
methods: {
// 方式一
// ...mapMutations({setUserInfo: "user/setUserInfo"}),
// ...mapActions({asyncUserInfo: "user/asyncUserInfo"})
// 方式二(参数2可以是数组)
// ...mapMutations("user", {setUserInfo: "setUserInfo"}),
// ...mapActions("user", {asyncUserInfo: "asyncUserInfo"})
// 方式三(参数2可以是数组)
...userMutations({setUserInfo: "setUserInfo"}),
...userActions({asyncUserInfo: "asyncUserInfo"})
},*/
setup() {
const store = useStore()
const storeState = userState(["userInfo"])
const state = {}
Object.keys(storeState).forEach(fnKey => {
const fn = storeState[fnKey].bind({$store: store})
state[fnKey] = computed(fn)
})
const storeGetters = userGetters(["getUserInfo"])
const getters = {}
Object.keys(storeGetters).forEach(fnKey => {
const fn = storeGetters[fnKey].bind({$store: store})
getters[fnKey] = computed(fn)
})
const storeMutations = userMutations(["setUserInfo"])
const storeActions = userActions(["asyncUserInfo"])
return {
...state,
...getters,
...storeMutations,
...storeActions
}
}
}
</script>
二十、补充知识点
1、nexttick
<template>
<button @click="clickHandler">按钮</button>
<p style="width: 100px" ref="pEl">{{message}}</p>
</template>
<script>
import {ref, nextTick} from "vue"
export default {
setup() {
const message = ref("")
const pEl = ref(null)
const clickHandler = () => {
message.value += "黄婷婷孟美岐"
/**
* 将回调推迟到下一个DOM更新周期之后执行(Promise方式添加到微任务队列)
* (packages/runtime-core/src/scheduler.ts:216)
*/
nextTick(() => {
console.log(pEl.value.offsetHeight)
})
}
return {
message,
clickHandler,
pEl
}
}
}
</script>
2、historyApiFallback
server {
location / {
# nginx按配置顺序查找资源,$uri是路径变量
try_files $uri $uri/ /index.html;
}
}
module.exports = {
configureWebpack: {
devServer: {
/**
* 所有查找不到的资源,都会返回/index.html。
* webpack-dev-server默认配置是false,
* vue-cli配置成了true,所以并不需要配置此项
*/
historyApiFallback: true
}
}
}