架构-vue3笔记

1.Monorepo介绍
    Monorepo 是管理项目代码的一个方式,指在一个项目仓库(repo)中管理多个模块/包(package)
    优点:
    一个仓库可维护多个模块,不用到处找仓库
    方便版本管理和依赖管理,模块之间的引用,调用都非常方便
    缺点:仓库体积会变大。
2.Vue3项目结构
    reactivity:响应式系统
    runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
    runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
    runtime-test:用于测试
    server-renderer:用于服务器端渲染
    compiler-core:与平台无关的编译器核心
    compiler-dom: 针对浏览器的编译模块
    compiler-ssr: 针对服务端渲染的编译模块
    compiler-sfc: 针对单文件解析
    size-check:用来测试代码体积
    template-explorer:用于调试编译器输出的开发工具
    shared:多个包之间共享的内容
    vue:完整版本,包括运行时和编译器
3.实现响应式
    1.实现reactive:packages\reactivity\src\reactive.ts
        1)reactive的数据会经过new Proxy处理返回一个代理对象,获取或者设置代理对象属性时会走mutableHandlers的get和set(Reflect获取或者设置对象属性,设置成功返回true失败返回false)
        2)通过new WeakMap缓存代理对象(即reactive两次相同的对象,返回的代理对象是同一个)
        3)reactive返回的代理对象再次经过reactive返回的是同一个代理对象,第二次reactive时会走mutableHandlers的get方法给代理对象增加一个属性表示对象是否被代理过,
        在createReactiveObject方法中如果对象是已经被代理过了,那么返回本身即可
    2.实现effect:packages\reactivity\src\effect.ts
        // 组件初次渲染执行回调,依赖的属性变化也会执行回调
        // runner()强制执行回调
        // runner.effect.stop()关掉effect
        let runner = effect(() => {
            console.log('ok')
            app.innerHTML = state.name + '今年' + state.age + '岁了' + state.age;
        })
        runner();
        runner.effect.stop();
        1)组件首次调用执行effect的run方法,在run方法中收集每个属性和它依赖的effect,每个effect和它依赖的属性(在effect回调中取值时会走get方法,在get方法中调用track方法进行收集)
        2)设置属性值时会走set方法执行trigger方法执行属性对应的effect的run方法即可(即执行回调)
        3)执行effect的返回值函数即执行effect的run方法即可(即执行回调)
        4)执行runner.effect.stop()相当于执行effect上面的stop方法解除effect对应的属性上面的effect,这样属性改变对应的effect不执行
    3.总结
        1.取属性值时,搜集每个属性上面的effect,effect对应的每个属性
        2.设置值时,调用每个属性上面的effect
        3.取消effect,就是取消每个effect上面的属性的effect
4.实现计算属性packages\reactivity\src\computed.ts
    // computed的参数可以是对调也可以是对象,回调相当于get
    // myAge.vaule取值
    // 取值时走get,设置值时走set
    // 取值,值不变走缓存
    const myAge = computed({ 
        get() { // 计算属性的缓存 (dirty)
            return state.age + 18;
        },
        set(value) { // 依赖收集是取值的时候收集,set不需要
            console.log(value)
        }
    })
    1. 计算属性是一个effect,回调是getter(new ReactiveEffect)
    2. 计算属性的依赖属性会收集computed这个effect(effect依赖的属性会自动收集回调,计算属性中将get作为回调传入)
    3. 计算属性在effect中使用,会收集对应effect方法(计算属性也是一个属性,也可以成为effect回调依赖的属性,trackEffects中收集effect) 
    4. 第一执行effect 时会取computed的值,dirty变为false;
       此时多次执行会走缓存
    5.计算属性依赖的值发生变化了, dirty变为true; 触发计算属性依赖值收集的effect
      在次取计算属性的值 因为dirty为true 会重新计算计算属性的值(triggerEffects)
5.实现ref:packages\reactivity\src\ref.ts
    // 放对象和基本属性都能返回一个响应数据,state.vaule取值
    const state = ref(0);
    const state = ref(obj);
    1.ref是一个RefImpl对象,取值时收集对应effect,返回值
    2.设置值时触发对应effect
6.实现模板编译packages\runtime-dom
    let App = {
        props: {
            title: {}
        },
        // setup可以返回一个对象,返回的就是模板所需要的数据,
        // 也可以返回一个函数,相当于render函数,返回模板
        setup(props, ctx) {
            return {
                flag
            }
        },
        // 每次更新重新调用render方法
        // proxy就是setup返回的数据,数据也可以通过this来获取
        render(proxy) {
            return h('h1', { onClick:this.add,title:proxy.title},this.count.value)
        }
    }
    // createApp第二个参数为组件接受的属性
    let app = createApp(App, { title: 'zf', a: 1 });
    app.mount('#app');
    1.模块依赖
        runtime-dom依赖runtime-core,runtime-core依赖reactivity(可以使用依赖包暴露出来的方法)
        1)实现操作dom的一些方法放在renderOptions对象中,包含dom的增删改查和dom属性的更新操作(packages\runtime-dom\src\index.ts)
        2)createApp方法就是packages\runtime-core\src\apiCreateApp.ts文件的createAppAPI,方法内调用render方法将虚拟节点渲染到指定
          容器内,render方法相当于packages\runtime-core\src\rendener.ts文件中的render方法
        3)虚拟节点通过packages\runtime-core\src\createVNode.ts文件中的createVNode方法创建
        4)render方法拿到虚拟节点和要挂载的容器通过调用patch方法将组件挂载在页面上和完成组件的更新
        5)processComponent方法中的mountComponent方法处理组件,通过createComponentInstance方法获得组件实例,通过setupComponent方法给实例属性赋值
        6)调用setupRenderEffect中调用render得到虚拟节点subTree,拿着虚拟节点subTree调用patch方法将虚拟节点变成真实节点插入到页面中
        7)diff算法(那新老虚拟dom调用patch比较)
            1.元素类型不一致直接干掉老元素创建新元素
            2.元素一样就开始对比服用元素的属性和孩子(patchElement方法)
            3.比较孩子
                1)之前是数组 , 现在是文本   删除老的节点 ,用新的文本替换掉
                2)之前是数组 , 现在也是数组  比较两个儿子列表的差异  (* diff算法)
                    1.创建新老节点的尾指针e1和e2,i是从头开始比较时会加一
                    2.从头开始比较复用节点,碰到不可复用的节点开始从后比较复用节点
                    3.i > e1并且i <= e2说明有新增的元素,但是往前新增还是往后新增不知道,通过anchor参照物找到新增的位置(老少新多)
                    4.i > e2并且i <= e1说明有要删除元素,删除多余元素即可(老多新少)
                    5.未知序列对比:根据新的节点 创造一个映射表 , 用老的列表去里面找有没有,如果有则复用,没有就删除。 最后新的多余在追加,
                      复用节点时通过了最长递增子序列算法来尽可能的复用节点
                      最长递增子序列算法:传入数组[2, 3,1,5,6,8,7,9,4],得到数组[2, 3,5,6,7,9]

                3)之前是文本, 现在是是空   直接删除老的即可
                4)之前是文本  现在也是文本 直接更新文本
                5)之前是文本 现在是数组  删除文本 新增儿子
                6)之前是空,现在是文本,直接更新文本 

7.实现vuex4
    1)用法
        main.js文件:
            import { createApp } from 'vue'
            import App from './App.vue'
            import store from './store'
            // Vue.use(store) 插件的用法, 会默认调用store中的install方法
            // 可以指定第二个参数,隔离多个store,组件中const store = useStore('my');
            createApp(App).use(store,'my').mount('#app')
        store/index.js文件:
            import { createStore } from 'vuex'
            const store = createStore({
                strict: true, // 开启严格模式, 不允许用户非法操作状态 (只能在mutation中修改状态,否则就会发生异常)
                plugins: [ // 会按照注册的顺序依次执行插件,执行的时候会把store传递给你
                    customPlugin:function customPlugin(store) {
                        let local = localStorage.getItem('VUEX:STATE');
                        if (local) {
                            // 替换store中的state
                            store.replaceState(JSON.parse(local))
                        }
                        // 调用commit方法后,会执行subscribe,将commit的{ type, payload }和store中的state传过来
                        store.subscribe((mutation, state) => { // 每当状态发生变化 (调用了mutation的时候 就会执行此回调)
                            localStorage.setItem('VUEX:STATE', JSON.stringify(state))
                        })
                    }
                ],
                state: { // 组件中的data
                },
                getters: { // 计算属性 vuex4 他并没有实现计算属性的功能
                },
                mutations: { // 可以更改状态 必须是同步更改的
                },
                actions: { // 可以调用其他action,或者调用mutation
                },
                modules: { // 子模块 实现逻辑的拆分 
                    aCount: {
                        namespaced: true, //需要加上namespaced
                        state: { count: 0 },
                        mutations: {
                            add(state, payload) { // aCount/add
                                state.count += payload
                            }
                        },
                    }
                }
            })
            export default store;
        App.vue组件中
            import { useStore } from 'vuex'
            import { computed, toRefs } from 'vue'
            setup() { // vue3 有个compositionApi的入口
                const store = useStore();
                function add() {
                store.commit('add', 1)
                store.commit('aCount/add', 1)//模块化
                }
                function asyncAdd() {
                store.dispatch('asyncAdd', 1)
                }
                return {
                count: computed(() => store.state.count), //使用computed返回的数据才能有响应式
                double: computed(() => store.getters.double),
                aCount: computed(() => store.state.aCount.count), //模块化
                add,
                asyncAdd
                }
            }
            //{{ count }}新的写法,{{ $store.state.count }}老的写法依然可以用
           

计数器:{{ count }} {{ $store.state.count }}

    2)源码
        1.实现createStore方法:vuex/index.js文件中createStore方法返回一个store对象即可
        2.实现install方法:vuex/store.js文件中app.provide将store放在全局
        3.实现useStore方法:vuex/injectKey.js文件使用inject拿到store
        4.state:通过reactive响应式包装用户传入的state
        5.getters:将用户传入的getters放在store.getters上即可(目前没有缓存功能,内部使用defineProperty实现,没有使用computed实现,因为vue版本内部问题)
        6.实现actions:将用户action订阅在store._actions上,调用dispatch方法时取出对应方法调用即可
        7.实现mutations:将用户action订阅在store._mutations上,调用commit方法时取出对应方法调用即可
        8.模块化
            1)格式化用户传入的数据:通过ModuleCollection类将用户传入的数据格式化成树结构放在store的_modules属性上(root/children的树结构)
            2)模块化state:通过installModule将state/Getters/Action/Mutation以树结构格式化在store的_state/wrappedGetters/_actions/_mutations上
            3)获取state时,直接去store._state上取,用户调用dispatch或commit直接去_actions/_mutations上取
        9.严格模式(只能在commit中修改state):store上加上一个属性commiting,默认为false,调用commit时将commiting变为true,使用watch监听state是否被修改,
            修改的情况下commiting为false,提示报错
        10.实现plugins:模块化后调用subscribe订阅插件,commit后调用订阅的插件即可
        11.replaceState:用新state替代老的state即可
    3)registerModule用法
        注册模块,可以实现登录后再去注册模块
        //['aCount', 'cCount']给aCount模块下面增加一个cCount
        //'aCount'给aCount模块增加内容
        store.registerModule(['aCount', 'cCount'], {
            namespaced: true,
            state: {
                count: 100,
            },
            mutations: {
                add(state, payload) {
                    state.count += payload
                }
            },
        })
8.router4
    1)使用
        1.router/index.js文件
            import { createRouter, createWebHistory,createWebHashHistory } from 'vue-router'
            import Home from '../views/Home.vue'
            import About from '../views/About.vue'
            const routes = [
                {
                    path: '/',
                    name: 'Home',
                    component: Home,
                },
                {
                    path: '/about',
                    name: 'About',
                    component:About
                }
            ]
            const router = createRouter({ 
                //相当于之前的mode,createWebHistory是History模式,createWebHashHistory是hash模式
                history: createWebHistory(),
                routes
            })
            export default router
        2.main.js文件
            mport router from './router'
            createApp(App).use(router).mount('#app')
        3.App.vue文件
           
           
    2)原理
        1.实现createWebHashHistory,createWebHistory:history/html5.js文件中,hash和History路由都使用window.History来实现,
        返回一个history对象,包含当前路由地址参数和路由跳转方法
        2.实现createRouter
            1)实现install方法:注册RouterLink和RouterView组件
            2)扁平化用户传入的routes:createRouterMatcher中实现
            3)实现$router和$route:使用app.config.globalProperties和app.provide全局注入这两个属性
        3.实现RouterLink组件:内部是a标签,跳转时调用router.push方法即可
        4.实现router-view组件:根据匹配到的路径渲染对应组件即可
    3)a组件进入b组件路由钩子调用顺序
        a组件:beforeRouteLeave
        全局的 beforeEach
        b组件beforeRouteUpdate
        路由beforeEnter。
        b组件beforeRouteEnter。
        全局的 beforeResolve
        全局afterEach

9.vite
    1)下载
        pnpm install vite -D
    2)启动 vite
        Vite 主要由两部分组成
        (开发环境)一个开发服务器,它基于 原生 ES 模块 提供了 丰富的内建功能,如速度快到惊人的 模块热更新(HMR),Vite 将会使用 esbuild 预构建依赖。Esbuild 使用 Go 编写,并且比以 JavaScript 编写的打包器预构建依赖快 10-100 倍
        (生产环境)一套构建指令,它使用 Rollup 打包你的代码,并且它是预配置的,可输出用于生产环境的高度优化过的静态资源
        1.package.json
            {
                "scripts": {
                    "dev": "vite",
                    "build": "vite build"
                }
            }
        2.vite.config.js
            import { defineConfig } from "vite"
            export default defineConfig({})
        3.src\env.d.ts
            Vite 默认的类型定义是写给它的 Node.js API 的,要将其补充到一个 Vite 应用的客户端代码环境中
            客户端类型
            如果你的库依赖于某个全局库
            使用/// 指令
            三斜线指令仅可放在包含它的文件的最顶端
            三斜线引用告诉编译器在编译过程中要引入的额外的文件
            ///
        4. src\main.ts
            export function render() {
            document.getElementById("app")!.innerHTML = "main";
            }
            render();

            if (import.meta.hot) {
            import.meta.hot.accept((updatedModule) => updatedModule.render());
            }
    3)支持 vue3
        1.安装 vue
            pnpm install vue
            pnpm install @vitejs/plugin-vue -D
        2.vite.config.ts
            import { defineConfig } from 'vite'
            +import vue from "@vitejs/plugin-vue";
            export default defineConfig({
            + plugins: [vue()]
            })
        3.src\env.d.ts
            ///
            +declare module "*.vue" {
            +  import type { DefineComponent } from "vue";
            +  const component: DefineComponent<{}, {}, any>;
            +  export default component;
            +}
    4)支持 typescript
        vue-tsc可以对 Vue3 进行 Typescript 类型较验
        pnpm install typescript vue-tsc  -D
        //package.json
        {
            "scripts": {
                "dev": "vite",
            +   "build": "vue-tsc --noEmit && vite build"
            },
            }
    5)ESLint
        1.安装
        pnpm install eslint eslint-plugin-vue  @typescript-eslint/parser @typescript-eslint/eslint-plugin @vue/eslint-config-typescript   -D
        //.eslintrc.js
        module.exports = {
        root: true,
        env: {
            browser: true,
            es2021: true,
            node: true
        },

        extends: [
            "plugin:vue/vue3-recommended",
            "eslint:recommended",
            "@vue/typescript/recommended"
        ],
        parserOptions: {
            parser: "@typescript-eslint/parser",
            ecmaVersion: 2021
        },
        rules: {
            "no-unused-vars": "off",
            "vue/no-unused-vars": "off",
            "@typescript-eslint/no-unused-vars": "off"
        },
        globals: {
            defineProps: "readonly"
        }
        }
        //.eslintignore
        node_modules
        dist
        *.css
        *.jpg
        *.jpeg
        *.png
        *.gif
        *.d.ts
        //package.json
        {
            "scripts": {
            "dev": "vite",
            "build": "vue-tsc --noEmit && vite build",
        +    "lint": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet",
        +    "lint:fix": "eslint --ext .ts,.tsx,vue src/** --no-error-on-unmatched-pattern --quiet --fix"
        },
        }
    6)Prettier(ESLint会按照Prettier风格来校验代码)
        prettier主要解决的是代码风格问题
        pnpm install prettier eslint-plugin-prettier  @vue/eslint-config-prettier -D
        //.eslintrc.js
        extends: [
            "plugin:vue/vue3-recommended",
            "eslint:recommended",
            "@vue/typescript/recommended",
        +   "prettier",
        +   "@vue/eslint-config-prettier"
        ],
        rules: {
        +   "prettier/prettier": [
        +     "error",
        +     {
        +       singleQuote: false,
        +       tabWidth: 2,
        +       indent: 2,
        +       semi: false,
        +       trailingComma: "none",
        +       endOfLine: "auto"
        +     }
        +   ],
        +   "no-unused-vars": "off",
        +   "vue/no-unused-vars": "off",
        +   "@typescript-eslint/no-unused-vars": "off"
        + },
        //.prettierrc.js
        module.exports = {
        singleQuote: false, //使用单引号
        semi: false, 末尾添加分号
        tabWidth: 2,
        trailingComma: "none",
        useTabs: false,
        endOfLine: "auto"
        }
        //.prettierignore
        node_modules
        dist
        //.editorconfig
        root = true
        [*]
        charset = utf-8
        indent_style = space
        indent_size = 2
        end_of_line = lf
    7)lint-staged(提交前做eslint检查)
        pnpm install mrm  -D
        npx mrm lint-staged
    8)commitlint(commit信息检查)
        pnpm install @commitlint/cli @commitlint/config-conventional -D
        npx husky add .husky/commit-msg "npx --no-install commitlint --edit $1"
        //commitlint.config.js
        module.exports = {
        extends: ["@commitlint/config-conventional"],
        rules: {
            "type-enum": [
            2,
            "always",
            [
                "build",
                "chore",
                "ci",
                "docs",
                "feature",
                "fix",
                "perf",
                "refactor",
                "revert",
                "style",
                "test"
            ]
            ]
        }
        }
    9)配置别名
        //vite.config.ts
        import { defineConfig } from "vite"
        +import { resolve } from "path"
        import vue from "@vitejs/plugin-vue"
        export default defineConfig({
        + resolve: {
        +   alias: {
        +     "@": resolve("src")
        +   }
        + },
        plugins: [vue()]
        })
        //tsconfig.json
        {
        "compilerOptions": {
            "target": "esnext",
            "module": "esnext",
            "moduleResolution": "node",
            "strict": true,
            "jsx": "preserve",
            "sourceMap": true,
            "esModuleInterop": true,
            "lib": ["esnext", "dom", "es2018.promise"],
        +   "baseUrl": ".",
        +   "paths": {
        +     "@/*": ["src/*"]
        +   }
        },
        "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
        }
    10)样式
        1.css(默认支持)
        2.less和sass
            pnpm install sass less -D
            //局部默认支持
           
            +
            +
            //全局
            //vite.config.ts
            import { defineConfig } from "vite"
            import { resolve } from "path"
            import vue from "@vitejs/plugin-vue"
            export default defineConfig({
            resolve: {
                alias: {
                "@": resolve("src")
                }
            },
            plugins: [vue()],
            + css: {
            +   preprocessorOptions: { //这边帮你引入文件
            +     scss: {
            +       additionalData: '@import "@/styles/theme.scss";'
            +     },
            +     less: {
            +       additionalData: '@import "@/styles/theme.less";'
            +     }
            +   }
            + }
            })
            //src\styles\theme.less
            $color: green;
            //src\styles\theme.scss
            $color: green;
            //src\components\HelloWorld.vue
           
           
    11)PostCSS(给样式加前缀,兼容不同浏览器)
        pnpm install autoprefixer  -D
        //postcss.config.js
        module.exports = {
        plugins: [require("autoprefixer")]
        }
        // .browserslistrc
        >0.2%
        not dead
        not op_mini all
    12)静态资源处理    
        public 目录
        如果有以下需求
        这些资源不会被源码引用(例如 robots.txt)
        这些资源必须保持原有文件名(没有经过 hash)
        那么你可以将该资源放在指定的 public 目录中,它应位于你的项目根目录
        该目录中的资源在开发时能直接通过 / 根路径访问到,并且打包时会被完整复制到目标目录的根目录下
        //index.html
       
       
       
    13)mock
        下载
        npm i mockjs vite-plugin-mock -D
        //vite.config.js
        +import { viteMockServe } from "vite-plugin-mock";
        +  plugins: [vue(),viteMockServe({})]
        //mock\test.ts根目录创建mock目录
        import { MockMethod } from 'vite-plugin-mock';
        export default [
            {
                url: '/api/get',
                method: 'get',
                response: ({ query }) => {
                    return {
                        code: 0,
                        data: {
                            name: 'vben',
                        },
                    };
                },
            },
        ]
    14)axios:和其它项目一样
    15)状态管理:和其它项目一样
    16)路由:和其它项目一样
    17)环境变量和模式
        Vite 在一个特殊的 import.meta.env 对象上暴露环境变量
        //.env.development根文件下创建
        VITE_APP_WEB_URL = "/dev"
        //.env.production
        VITE_APP_WEB_URL = "/prod"
        //src\main.ts
        +console.log(import.meta.env)//拿到环境变量
        //package.json
        {
        "scripts": {
        +    "build:dev": "vue-tsc --noEmit && vite build --mode development",
        +    "build:prod": "vue-tsc --noEmit && vite build --mode production",
        },
        }

学到21.37
 

你可能感兴趣的:(笔记,vue.js,javascript)