一路繁花似锦绣前程
失败的越多,成功才越有价值

导航

 

十六、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
* 可响应式系统模块
* 应用程序入口模块
  • index.html
<!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>
  • renderer.js
// 一、渲染系统模块
// 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)
                    })
                }
            }
        }
    }
}
  • reactive.js
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()
        }
    })
}
  • index.js
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
  • setupComponent断点调试
函数 vue.global.js
setupComponent 5398
setupStatefulComponent 7679
handleSetupResult 7739
finishComponentSetup 7771
compile 7792
applyOptions 7812
  • compile断点调试
函数 vue.global.js
compile 7792
registerRuntimeCompiler 7780
compileToFunction 13765
compile$1 13791
baseCompile 13746
baseParse 13302
transform 13304
generate 13313
  • setupRenderEffect断点调试
函数 vue.global.js
setupRenderEffect 5414
renderComponentRoot 5472
normalizeVNode 2090
openBlock 6473
createBlock 6511
patchBlockChildren 5344
  • applyOptions断点调试
函数 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

1、安装devtool
* 手动安装
    - 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
  • nginx配置
server {
    location / {
        # nginx按配置顺序查找资源,$uri是路径变量
        try_files $uri $uri/ /index.html;
    }
}
  • vue.config.js
module.exports = {
    configureWebpack: {
        devServer: {
            /**
             * 所有查找不到的资源,都会返回/index.html。
             * webpack-dev-server默认配置是false,
             * vue-cli配置成了true,所以并不需要配置此项
             */
            historyApiFallback: true
        }
    }
}
posted on 2022-04-19 12:35  一路繁花似锦绣前程  阅读(267)  评论(0编辑  收藏  举报