ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

vue源码的学习

2021-09-10 17:30:38  阅读:103  来源: 互联网

标签:function Vue vue route 学习 源码 组件 data ###



```html
1.  ### vue 渐进式框架(易用灵活高效)

    在使用的时候可以 不用安装 vue-cli 不需要 babel 转义
    比起 react 要轻很多 只需要直接 script 导入就可以完成 helloword

    ####易用

    封装了比如 v-model v-if v-show v-for v-else v-html 等等 指令
    作用于 dom 元素 替代原生的操作 比如 v-if 的操作
    用于 document.createElement document.getElementById().remove()
    来创建元素 移除 元素 其他指令后续介绍

    ####灵活

    提供了全局 api 与组件实例 api 方便我们操作 dom 与数据流 比如 this.$refs 用来获取节点
    Vue.use 拓展插件 带 Vue.xxx 的全局 api 用作于 所有的组件 关于为何用于所有的组件后续介绍

    ####高效
    采用了单文件组件的模板 让 html js css 分开 利于维护 观察 数据流的变化
    提供了组件的复用 mixin mixins provide inject props 等等可供数据传递的方法
    解决组件的使用场景 比如 父子组件 子孙组件 兄弟组件 陌生组件 关系直接的数据流动

2.  ###基于起步 vue 导入

    ####下载 js 文件导入
    挂载使用 new Vue({el:'选择器'}) el 表示 document 选择器 class 类选择器 id 选择器 等等都可以
    data 是对象 亦可以是函数 区别在于 函数作用域独立 不会影响各自组件
    对象存在引用关系 如图 a 变量所示 所有东西都是响应式的
    也可以使用
    new Vue({
    render: (h) => h(App), // app 组件
    }).$mount("#app"); // $mount 挂载的节点
    new Vue({
      components: { App },
      template: `<App/>`,
    }).$mount("#app")

    ####单文件组件模板 使用 render template el(只作用于 root 组件)
    如果使用多个 会根据 render > template > el 来渲染结果
    因为 不管 el 还是 template 都会经过转化 变成 render 函数 来进行 虚拟 dom 的对比
    虚拟 dom 不是 vue 框架特有的 可以说是借鉴了其他框架
    关于虚拟 dom 也就类似于把模板里面的同名的 html 标签 比如 div p 这些当做字符串来进行正则匹配
    最终处理成树结构对象字面量的形式 因为结合树的关系
    提供了 类型与 this.$children this.$parent 函数来获取 父节点 与 子节点
    特别注意的是 里面有很多自定义的标签 比如 hello el-input van-button u-button v-button 等等
    诸如此类的组件库里面使用的组件 关于这些自定义的组件 是如何识别的
    提供了一个全局 api 名字 vue.extend
    let textDocument = Vue.extend({
    props: {
    },
    methods: {
    },
    template: `<button @click="but"><span>as</span><child></child></button>`
    })

    ####用于解析单文件组件
    一个组件里面的模板内容 包含 同名的 html 标签字符串与自定义的组件名称
    同名的标签就直接解析 自定义的组件 就通过 Vue.extend 解析
    然后挂载在组件实例的 this.$children上面 然后形成了 组件有父子级 甚至可以通过不断的$parent $children
    来找自己的子辈 组辈 从而追溯到树的根 this.$root 在编译的时候通过关系的追溯 从而实现从上到下的编译 从下到上的挂载
    比如 如上的 button 会形成 vnode 虚拟节点 里面有 children 数组 包含了 span 标签 child 自定义组件
    组件与标签会通过编译返回 对象字面量的虚拟节点 tag children text 两者很多属性都一样
    不一样的是组件会有 VueComponent 自己特有的属性 比如 data 里面有特有的方法 这些方法就会去启动生命周期函数 与响应式数据拦截

3.  ### 如何实现响应式

    data 函数返回 对象 被 Object.defineProperty 做数据拦截
    触发了 VueComponent 的观察者 它通知组件 做渲染
    不管是 v-model 还是 input 等等改变数据的方法 如果没有在 data 里面写入 观察者就不会去做拦截
    组件也就不会刷新 有一些特定的场景下面也存在不刷新页面的情况

    ####讨论响应式的原理
    Vue.observable( object )
    让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。
    返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新
    这个 api 会把 data 数据做拦截
    使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
    Object.defineProperty 是 ES5 中一个无法 支持 的特性,
    这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因

    在转 getter 的时候 会把 data 里的属性与 value 存在一个 Dep 依赖数组里面 根据存进去加的唯一值 key
    let data = function () { // 数组的 data
    return {
    message: 'Hello Vue!',
    text: 123
    }
    }
    let data1 = data()
    let c = {} // c 用于数据拦截的对象 也是替换模板 监听 渲染 变量 的 实质对象
    Object.keys(data1).map(res => {
    Object.defineProperty(c, res, {
    enumerable: true,
    configurable: true,
    get() { return data1[res]; }, // 获取初始化指 会存入 Dep 数组
    set(newValue) { data1[res] = newValue; console.log('改变', newValue); },// 设置属性 dep.notify();触发通知

    })
    })

    // 设置属性 dep.notify();触发通知 告诉 watcher 对象 去执行 run 函数 触发 update 函数
    // 触发 update 函数 => 再走一遍初始化的流程 把变量 替换 成最新的
    if (!config.async) {
    flushSchedulerQueue();// resetSchedulerState();callActivatedHooks(activatedQueue);

    callUpdatedHooks(updatedQueue);
    return;
    }
    nextTick(flushSchedulerQueue);

    // Vue.prototype.$forceUpdate = function () {
    var vm = this; //组件实例
    if (vm.\_watcher) {
    vm.\_watcher.update();
    }
    };

4.  ### 响应式的失效

    如上我们知道 之所以有响应式 是 因为 数据拦截 触发 update 函数 页面同步渲染
    失效 也就是 有时候 我们的操作 不会 进行数据拦截 由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。
    尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。
    对于对象 新增 属性 this.$set(this.someObject,'b',2)
    对于数组 this.$set(arr, index, newValue) 修改 数组 指定位置
    删除对象 与 属性 this.$delete(obj,key)

    其实对于数组 我们通常 使用 新数组 替换 旧数组 来 保证 响应式

    ####使用了 object.freeze()
    function observe(value, asRootData) {
    if (!isObject(value) || value instanceof VNode) {
    return;
    }
    var ob;
    if (hasOwn(value, "**ob**") && value.**ob** instanceof Observer) {
    ob = value.**ob**;
    } else if (
    shouldObserve &&
    !isServerRendering() &&
    (Array.isArray(value) || isPlainObject(value)) &&
    Object.isExtensible(value) &&
    !value.\_isVue
    ) { // 如果不具有 Object.isExtensible 也就是不能修改的对象
    ob = new Observer(value);
    }
    if (asRootData && ob) {
    ob.vmCount++;
    }
    return ob; // 直接返回结果
    }

5.  ### 生命周期函数

    ####单组件

    beforeCreate 把 vue 实例初始化,数据方法还没有加载
    created 已经加载数据方法
    beforeMount 模板数据已经编译
    mounted 渲染视图
    前面四个是组件初始化加载经过的生命周期函数
    beforeUpdate 没有修改数据
    updated 修改之后
    beforeDestroy 组件销毁之前
    destroyed 销毁组件 这个经常用于切换各个组件销毁定时器
    deactivated 缓存组件激活
    activated 缓存组件失活 外加一个错误捕获 errorCaptured 当捕获一个来自子孙组件的错误时被调用

    ####多组件
    解析:Vue 的父组件和子组件生命周期钩子函数执行顺序可以归类为以下 4 部分:
    加载渲染过程 : 父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created ->子 beforeMount -> 子 mounted -> 父 mounted
    子组件更新过程 : 父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
    父组件更新过程 : 父 beforeUpdate -> 父 updated
    销毁过程 : 父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed

6.  ### 模板语法 指令 事件 按键修饰符(不会被最新浏览器支持)
7.  ### 计算属性 来源于 响应式 data 数据的处理结果 根据不同的需求处理不一样的结果
    比如 一个数组 let arr = [{sex:0,name:'小美'},{sex:1,name:'小明'}]
    computed: {
    boy: function(){
    return this.arr.filter(res=>{return res.sex === 1})
    },
    girl:function(){
    return this.arr.filter(res=>{return res.sex === 0})
    },
    }
8.  ###侦听器
    监听的范围:
    props,
    inject
    当前组件的 data
    计算属性
    路由信息$route
    vuex 的初始化 getter
    包含所以的数据类型
    数组监听下标
    对象可以表示为 key
    ‘a.b’

    #### 计算属性与侦听器对比

    计算属性是通过 data 里面的变量综合处理返回后的结果 避免了模板里面逻辑过重

    侦听器是监听 data 里面某一个变量来触发后续变化
    使用场景 计算属性 用于处理数据返回后的结果
    侦听器通常建议是用于两种三种状态的切换 比如 1 2 3 三种结果可预期的
    true 与 false 这样的值
    但有时候不是状态的切换 但一定是可预期的结果 比如你知道变化后的数据类型等等

9.  ### 绑定 html class style

    表达形式 数组 对象 字符串
    <div :class="class1" :style="style">asd1</div>
    <tep :class="class1" a="1" b="1" id="1" />
    class1: ['a', 'b', { c: false }],
    style等于内置style样式 数组语法

    只不过 font-size 变化
    fontSize
    如此类似
    css 变量
    style: {
    border: '1px solid red'
    }

10. ### 条件渲染 v-if v-show v-for

    不推荐一起用 因为 每走一次 for 循环都会去执行 if 所以当你需要过滤数据展示
    你完全可以使用之前提过的计算属性 或者 你需要判断 数据 存在 后执行循环
    建议把 if 提出来 注意 v-for 与 key 必须一起用 有时候可以不用 但是
    存在数据修改 一定用 所以别忘记 因为 vue 对比新旧节点是通过 key 来的
    不建议使用 index 作为 key 因为数组变化 index 就已经不是之前的 index 会导致页面错误变化

    if 建议是控制元素在页面展示只有一种状态时使用比如存在 与不存在

    show 是切换页面使用控制隐藏显示

11. ### 数组更新检测
    可以改变原数组的方法 会被 vue 监听响应式
    push pop unshift shift sort reserve splice
    除去以上方法 其他都不改变原数组 除了存在引用关系例外
    filter()、concat() 和 slice() 等等之类的 返回新组件 替换数组
    然后这些 concat slice 就不会响应式 所以把新数组替换 data 的原数组
12. ### 监听事件 v-on 对应 onClick 原生监听操作
13. ### 表单输入绑定 v-model 的使用

    input 是 文本字符串
    radio 是 布尔值
    复选框 是数组
    表单 form 里面 不建议使用 button 因为默认会提交 通常都是 div 自行画的按钮
    由于登录之后 浏览器存在 默认行为 会自动 填充 输入框 所以需要注意
    修饰符.lazy .trim .number 这是处理输入文本

    #### 组件的使用

    <input v-model=“”/> 如果是在原生标签上面使用默认是 value 与@input 事件也就是原生

    js 的 oninput 与 e.target.value 和 value 实现一样的功能 但是有时候作用于复选框下拉框
    我们原生实现需要不同的写法 但 v-model 在编译的时候通过判断标签属性来动态处理
    基础组件指不与原生标签同名的标签 比如 my-input 之类的 自定义 在自定义上面使用
    v-model 就如上图所示 出现了 props 与 model 结合的案例
    就可以把自定义的 input 的组件
    当做 input 来实现
    model 有两个属性 prop 与 props 对应
    event 这个默认是 change 可以更改 然后子组件触发 this.$emit('change')

14. ### .sync 修饰符
    避免修改 props 的错误 提供了这个方法来改变父组件 @update:xxx xxx="a" this.$emit('update:xxx','x')
15. ### 插槽 默认插槽 具名插槽 插槽作用域
                    插槽 是父组件 分发给子组件的内容 切记 切记
          <template>
            <div id="app">
              <ss>
                <div>默认插槽</div>
                <div slot="app">hello</div>
                <div slot="data" slot-scope="user">//插槽作用域起了一个别名
                //类似于export default 封装方法与属性
                    <span v-for="a in user.data" :key="a.id">{{a.name}}</span>
                </div>
              </ss>
            </div>
          </template>
                    子组件
                默认插槽当父组件是除了具名插槽什么都没有才显示
          <template>
            <div>
                <slot>123q</slot>//默认插槽没有name
                <slot name="app">hello</slot>//具名插槽对应父组件slot="app"这个属性
                <slot name="data" :data="data"></slot>//具名插槽作用域
                //子组件使用slot绑定data数据,使用别名
                //在父组件调用子组件里面使用slot-scope插槽作用域来获取子组件数据
            </div>
        </template>
        <script>
        export default {
            data(){
                return {
                    msg:"",
                    data:[{id:1,name:"user"},{id:2,name:"pass"}]
                }
            }
        }
        </script>
16. ### 进入/离开 & 列表过渡 动画 结合 animate.css 库 一起用 可以尝试 官网推荐
17. ### 混入 分组件混入 全局混入
    数据以组件优先 钩子函数
    会合并 命名 data 与 methods
    不要冲突不然以组件的优先
    覆盖
18. ### 自定义指令 分全局与 组件内部

19. ### 单文件组件

20. ### 路由配置
    import Vue from "vue";
    import VueRouter from "vue-router";
    import Home from "../views/Home.vue";

Vue.use(VueRouter);

const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/404",
name: "404",
component: 404,
},
{
path: "/about",
name: "About",
props: (route) => {
return { query: route.query, params: route.params };
}, // this.$attrs {params: {},query: {a: "1"}}
component: () => import("../views/About.vue"),
component: function(resolve) {
return require(["../views/index.vue"], resolve); // 异步组件
},
},
{
path: '\*',
redirect: '/404'
}
];

const router = new VueRouter({
mode: "hash",
base: process.env.BASE_URL,
routes:[],
});

export default router;

21. ### 路由跳转 守卫 传参 获取

        beforeEach 还没进入页面 to from next 是一个函数 next执行进去页面
        beforeRouteEnter(to, from, next) {
          next({ path: "/about" });
        },
        路由跳转 push replace go back forward resolve // location.replace
        全局守卫 beforeEach  afterEach // 权限判断
        路由独享 beforeEnter
        组件守卫 beforeRouteEnter beforeRouteLeave beforeRouteUpdate
        addRoutes 新增路径 参数是一个数组 用于 把权限页面路由 与基础路由做合并
        push({path:'',query:{}})
        push({name: '',params:{}})
        // 组件守卫 beforeRouteEnter beforeRouteLeave beforeRouteUpdate
        // 通过在VueRouter进行合并策略 可以使全部组件里面与守卫函数一样的同名函数具有一样的效果
        Vue.config.optionMergeStrategies._my_option = function (parent, child, vm) {
          console.log(child)
          return child + 1
         }

        const Profile = Vue.extend({
          _my_option: 1
        })
        console.log(Profile.options._my_option)
        // Profile.options._my_option = 2

        Vue.mixin({

        beforeCreate: function beforeCreate () {
        if (isDef(this.$options.router)) { // this.$options.router 根组件
              this._routerRoot = this;
              this._router = this.$options.router;
              this._router.init(this);
              Vue.util.defineReactive(this, '_route', this._router.history.current);
        } else { // 根组件 下面的 子节点组件 都是通过获取父级信息来新增路由控制
          this._routerRoot = (this.$parent && this.$parent._routerRoot) || this;
        }
          registerInstance(this, this);
        },
          destroyed: function destroyed () {
          registerInstance(this);
          }
        });

        Object.defineProperty(Vue.prototype, '$router', {
        get: function get () { return this._routerRoot._router }
        }); // 由于组件在实例化的过程 是通过 Vue.extend 来实现的 所以this是指向一直都是Vue 位于Vue 原型上面的东西 也可以通过原型链向上查找 获取到对象

        Object.defineProperty(Vue.prototype, '$route', {
        get: function get () { return this._routerRoot._route }
        });

        Vue.component('RouterView', View);
        // 这个View 是函数组件functional = true 然后组件实例化的过程 已经有路由对象
        // 所以这是封装后的内置组件 通过之前初始化路由生成的 nameMap listMap pathMap
        // 也就是我们配置的routes 路由列表 然后根据 component参数与path 渲染出对应的/// render函数 形成虚拟的节点 最终转化成 dom 形成页面
        Vue.component('RouterLink', Link);
        // 这个是普通的render函数渲染的组件 通过已经存在的 路由对象
        // 提供参数方便我们调转 与函数手动调转 一致

        var strats = Vue.config.optionMergeStrategies;
        // 合并策略 在路由初始化激活的时候 就 给到了 内置 函数
        // 合并组件同名函数 提供权限判断
        strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created;

        this.matcher = createMatcher(options.routes || [], this);// 路由配置列表
        options.routes => routes:[]// 路由配置列表
        var ref = createRouteMap(routes);
        // 解析 路由配置列表 递归子路由 执行addRouteRecord
        // 然后生成nameMap listMap pathMap
        routes.forEach(function (route) {
              addRouteRecord(pathList, pathMap, nameMap, route, parentRoute);
        });
        route.children
        addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);




        function addRouteRecord (
            pathList,
            pathMap,
            nameMap,
            route,
            parent,
            matchAs
          ) {
            var path = route.path;
            var name = route.name;

            var record = {
              path: normalizedPath,
              regex: compileRouteRegex(normalizedPath, pathToRegexpOptions),
              components: route.components || { default: route.component },
              alias: route.alias
                ? typeof route.alias === 'string'
                  ? [route.alias]
                  : route.alias
                : [],
              instances: {},
              enteredCbs: {},
              name: name,
              parent: parent,
              matchAs: matchAs,
              redirect: route.redirect,
              beforeEnter: route.beforeEnter,
              meta: route.meta || {},
              props:
                route.props == null
                  ? {}
                  : route.components
                    ? route.props
                    : { default: route.props }
            };
            if (route.children) {
              {
                if (
                  route.name &&
                  !route.redirect &&
                  route.children.some(function (child) { return /^\/?$/.test(child.path); })
                ) {
                  // warn
                }
              }
              route.children.forEach(function (child) {
                var childMatchAs = matchAs
                  ? cleanPath((matchAs + "/" + (child.path)))
                  : undefined;
                addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs);
              });
            }
            if (!pathMap[record.path]) {
              pathList.push(record.path);
              pathMap[record.path] = record;
            }
            if (route.alias !== undefined) {
              var aliases = Array.isArray(route.alias) ? route.alias : [route.alias];
              for (var i = 0; i < aliases.length; ++i) {
                var alias = aliases[i];
                if ( alias === path) {
                  // warn
                  continue
                }
                var aliasRoute = {
                  path: alias,
                  children: route.children
                };
                addRouteRecord(
                  pathList,
                  pathMap,
                  nameMap,
                  aliasRoute,
                  parent,
                  record.path || '/' // matchAs
                );
              }
            }
            if (name) {
              if (!nameMap[name]) {
                nameMap[name] = record;
              } else if ( !matchAs) {
                // warn
              }
            }
          }

22. ### vuex

    import Vue from "vue";
    import vuex from "vuex";
    Vue.use(vuex);
    const state = {
    // 仓库
    msg: "欢迎你 我的朋友",
    dev: process.env.NODE_ENV,
    };
    const mutations = {
    // 同步提交
    mutHello: function(stata, data) {
    console.log(stata, data);
    stata.msg = "123";
    },
    };
    const actions = {
    // 分发 dispatch
    actHello: function({ commit }, data) {
    commit("mutHello", data);
    },
    };
    const getters = {
    // 获取属性
    hello(sta) {
    return sta.msg;
    },
    };
    const ma = {
    // 模块
    state: { num: 1 },
    mutations: {
    changeNum: function(sta, date) {
    console.log(date);
    sta.num++;
    },
    },
    actions: {
    actChangeNum({ commit }, date) {
    commit("changeNum", date);
    },
    },
    getters: {
    amin: function(sta) {
    return sta.num + "动画";
    },
    },
    namespaced: true,
    };
    const modules = {
    // 模块
    ma,
    };
    let store = new vuex.Store({
    state,
    mutations,
    actions,
    getters,
    modules,
    });

    store.subscribeAction((fn, state) => {
    // 监听异步
    console.log(fn, state);
    });
    store.subscribe((mutation, state) => {
    // 监听同步
    console.log(mutation); //
    console.log(state);
    });
    store.watch(
    (state, getters) => state.msg,
    (res) => {
    console.log("loaded", res); // 监听仓库
    }
    );
    export default store;

    mapState: mapState,
    mapMutations: mapMutations,
    mapGetters: mapGetters,
    mapActions: mapActions,
    createNamespacedHelpers: createNamespacedHelpers,
    // 同步 操作 必须这样改 配合 vue-devtools 调试工具 使用
    // 虽说可以直接 赋值 但 不会改变状态 不妥
    $store.commit('ma/changeNum'); => createNamespacedHelpers('ma') mapMutations(['changeNum'])
    $store.dispatch('ma/actChangeNum'); => createNamespacedHelpers('ma') mapActions(['actChangeNum'])

    $store.commit('mutHello')  => mapMutations(['mutHello'])
    $store.commit('actHello') => mapMutations(['actHello'])

    Vue.mixin({ beforeCreate: vuexInit });
    function vuexInit () { // 和路由一样的 初始化 子组件获取根节点组件 下一级获取父级
    var options = this.$options;
      // store injection
      if (options.store) {
        this.$store = typeof options.store === 'function'
    ? options.store()
    : options.store;
    } else if (options.parent && options.parent.$store) {
        this.$store = options.parent.$store;
    }
    }

23. ### sync
<template>
  <div>12{{ title }}</div>
</template>

<script>
export default {
  destroyed() {
    console.log("as");
  },
  props: {
    title: {
      type: String,
    },
  }, // 可以不写这个 使用 $attrs.title
  created() {
    this.$emit("update:title", "asa a");
  },
  // delimiters 改变模板{{}}
};
</script>

<style></style>

标签:function,Vue,vue,route,学习,源码,组件,data,###
来源: https://blog.csdn.net/qq_43505774/article/details/120226624

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有