Vue 4.0


# Vue 组件通信

  • 父传子 prop

  • 父传子 插槽 slot

  • 子传父 $emit

  • 子传父,定义 ref

  • vuex

  • 父传子,.sync

  • $children $parent 尽量别用,用也是取,尽量别改

    • 案例 1

      • parent1.vue

        <template>
          <div>
            <p>父组件的data:{{ count }}</p>
            <button @click="getSonNum">获取子组件的 number</button>
            <Son1 />
          </div>
        </template>
        
        <script>
          import Son1 from "./Son1";
          export default {
            name: "Parent1",
            components: { Son1 },
            data() {
              return {
                count: 2000,
              };
            },
            methods: {
              change(newValue) {
                this.count = newValue;
              },
              getSonNum() {
                console.log(this.$children[0].number);
              },
            },
          };
        </script>
      • son1.vue

        <template>
          <div>
            <p>我是子组件1,使用父组件的data {{ count }}</p>
            <button @click="handleClick">
              通过$parent调用父组件的事件修改父组件的data
            </button>
          </div>
        </template>
        
        <script>
          export default {
            name: "Son1",
            data() {
              return {
                number: 5,
              };
            },
            computed: {
              count() {
                // console.log(this.$parent.$data.count);
                // $data可以省略
                // console.log(this.$parent.count);
                return this.$parent.count;
              },
            },
            methods: {
              handleClick() {
                this.$parent.change(200);
              },
            },
          };
        </script>
  • 依赖注入 provide 和 inject

    • Parent2.vue

      <template>
        <div>
          <p>我是父组件2中的 data:{{ count }}</p>
          <Son2
            str="hello"
            class="active"
            title="xxx"
            id="fsfs"
            @click="
              () => {
                console.log('hello');
              }
            "
          />
        </div>
      </template>
      
      <script>
        import Son2 from "./Son2";
        export default {
          name: "Parent2",
          components: { Son2 },
          data() {
            return {
              count: 100,
            };
          },
          provide() {
            return {
              // 向下传递的时候,要写成函数带返回值的形式,这样后代才能动态获取
              // 只能写成箭头函数,不能写成普通函数,因为普通函数的this指向的是 return{}
              count: () => this.count,
              changeCount: this.changeCount,
            };
          },
          methods: {
            changeCount(newCount) {
              this.count = newCount;
            },
          },
        };
      </script>
    • Son2.vue

      <template>
        <div>
          <p>子组件2使用父组件2中的 data:{{ count() }}</p>
          <p>父组件传递的str props:{{ $attrs.str }}</p>
          <GrandSon2 />
        </div>
      </template>
      
      <script>
        import GrandSon2 from "./GrandSon2";
        export default {
          name: "Son2",
          components: { GrandSon2 },
          inject: ["count"],
          created() {
            // 获取当前组件的所有 html 属性,就是父组件使用我的时候标签内传递的所有属性,class除外
            console.log(this.$attrs);
            // 获取传递的事件
            console.log(this.$listeners);
          },
        };
      </script>
    • GrandSon2.vue

      <template>
        <div>
          <p>子子组件2使用父父组件2中的 data:{{ count() }}</p>
          <button @click="handleClick">使用inject获取祖先的修改事件并执行</button>
        </div>
      </template>
      
      <script>
        export default {
          name: "Parent2",
          inject: ["count", "changeCount"],
          methods: {
            handleClick() {
              this.changeCount(5000);
              // 如果provide只是传值的话只是修改了 Parent2,剩下的两个组件没有被修改
              // 把 provide 的 count 改成了函数,就会全部跟着修改
            },
          },
        };
      </script>

# 渲染函数 & JSX

灵活性比较强,想 是什么标签就是什么标签的时候用渲染函数或 jsx

  • 渲染函数 render (https://cn.vuejs.org/v2/guide/render-function.html)

    • RenderDemo.vue

      <template>
        <div>
          <!-- 需求:想创建一个组件,等级标题组件 -->
          <LevelTitle :level="1">标题1</LevelTitle>
          <LevelTitle :level="2">标题2</LevelTitle>
          <LevelTitle :level="3">标题3</LevelTitle>
        </div>
      </template>
      
      <script>
        import LevelTitle from "./LevelTitle";
        export default {
          name: "RenderDemo",
          components: { LevelTitle },
        };
      </script>
    • LevelTitle.vue

      <!-- 这里不能写template标签,因为下面使用了createElement -->
      <script>
        export default {
          name: "LevelTitle",
          props: ["level"],
          // 组件内的 render 函数,可以帮我们创建 template
          // 创建 template 需要使用 createElement 方法(是render 函数自带的参数)
          // createElement 的使用
          // 参数1 字符串(标签名)/对象/函数    必选
          // 参数2 对象(一个与模板中attribute对应的数据对象)     非必选
          // 该对象内可以设置 class style 事件绑定 prop ……
          // 参数3 字符串/数组  表示的都是标签的内容,注意标签添加子标签需要使用 createElement       非必选
          render(createElement) {
            return createElement("h" + this.level, this.$slots.default);
      
            /* return createElement(
              "h" + this.level,
              {
                class: "test",
                on: {
                  click() {
                    console.log(111);
                  },
                },
              },
              // [createElement("span", "哈哈哈哈"), "hello"]
              [
                this.$slots.default,
                this.level === 1 ? createElement("span", "我是标题1") : "",
              ]
            ); */
          },
        };
      </script>
  • JSX
    使用 jsx 简化 render 函数
    jsx 语法支持在 js 中写 html
    如果 jsx 语法中 在 html 内想要 js 的话 使用 {} 包裹

    • RenderDemo.vue

      <template>
        <div>
          <!-- 需求:想创建一个组件,等级标题组件 -->
          <LevelTitle :level="1">标题1</LevelTitle>
          <LevelTitle :level="2">标题2</LevelTitle>
          <LevelTitle :level="3">标题3</LevelTitle>
        </div>
      </template>
      
      <script>
        import LevelTitle from "./LevelTitle";
        export default {
          name: "RenderDemo",
          components: { LevelTitle },
        };
      </script>
    • LevelTitle.vue

      <script>
        export default {
          name: "LevelTitle",
          props: ["level"],
          render() {
            const tag = "h" + this.level;
            return (
              <tag onclick={this.x}>
                {this.$slots.default}
                {this.level === 1 ? <span>我是1</span> : ""}
              </tag>
            );
          },
          methods: {
            x() {
              console.log("x");
            },
          },
        };
      </script>

# 函数式组件

函数组件和普通组件没区别
如果 一个组件只接收 props 没有自己的 data ,没有 data 的组件可以称为无状态组件, 此时可以将这个组件设置成函数组件
两种组件使用和创建都一样,只不过 函数组件渲染会快

  • 单文件形式的组件

    • RenderDemo.vue

      <template>
        <div>
          <h3>函数组件</h3>
          <FunComponent :list="list" :handleClick="handleClick">
            <h4>测试 slot</h4>
          </FunComponent>
        </div>
      </template>
      
      <script>
        import FunComponent from "./FunComponent";
        export default {
          name: "RenderDemo",
          components: {
            FunComponent,
          },
          data() {
            return {
              list: [1, 2, 3],
            };
          },
          methods: {
            handleClick() {
              console.log("传递事件");
            },
          },
        };
      </script>
    • FunComponent.vue

      <template functional>
        <div>
          <!-- 函数组件一般不需要设置 script 标签 -->
          <!-- 接收的 props 直接在 template 使用 props.xx 接收并使用 -->
          <ul>
            <li @click="props.handleClick" v-for="item in props.list" :key="item">
              {{ item }}
            </li>
          </ul>
          <slot />
        </div>
      </template>
  • js 形式的组件

    • FunComponent1.js

      // 使用 js 文件创建组件
      // 局部组件
      // 普通的局部组件就是一个对象,对象内必须添加render方法,返回 vnode 节点
      /* export default {
        name: "FunComponent1",
        props: ["title"],
        render() {
          return (
            <div style="color:red">
              局部函数组件{this.title}
              {this.$slots.default}
            </div>
          );
        },
        created() {
          console.log(1111);
        },
      };
      */
      
      // js 文件的函数式组件创建
      // 当 functional 属性为 true 该组件就是函数组件,就不能使用 this
      // 想要获取组件的一切,需要使用 render 方法的第二个参数 context.
      // 详情参考文档
      // https://cn.vuejs.org/v2/guide/render-function.html#%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BB%84%E4%BB%B6
      /* export default {
        name: "FunComponent1",
        functional: true,
        render(h, context) {
          // context 上下文,组件的一切都是通过 context 去拿
          return (
            <div style="color:red">
              局部函数组件{context.props.title}
              {context.children}
              // {context.slots().default} // 这样写也可以
            </div>
          );
        },
      }; */
      import Vue from "vue";
      // 全局组件
      Vue.component("FunComponent1", {
        props: ["title"],
        render() {
          return <div>全局组件{this.title}</div>;
        },
      });
    • RenderDemo.vue

      <template>
        <div>
          <FunComponent1 title="hello" class="x" id="x">
            <span>world</span>
          </FunComponent1>
        </div>
      </template>
      
      <script>
        // import FunComponent1 from "./FunComponent1";
        // 注释了也能运行是因为 FunComponent1 全局注册了
        export default {
          name: "RenderDemo",
          components: {
            // FunComponent1,
          },
        };
      </script>
    • main.js

      import "./components/FunComponent1";

# 插件

Vue.use() 主要就是执行该对象里边的 install 方法
在 vue 中为 vue 添加全局功能
全局 property,过滤器,prototype ……

  • main.js

    import "./plugins/index";
    // 可以当作很多 js 的全局变量
    Vue.xx = "100";
    // 在所有 vue 组件中创建了一个全局变量
    Vue.prototype.$xx = 100;
  • PluginDemo.vue

    <template>
      <div>
        <!-- 有些时候使用 Vue.use 方法就给 vue 添加了一些全局功能 -->
        <input type="text" v-focus="true" />
        <!-- 这里的true就是binding.value -->
      </div>
    </template>
    
    <script>
      import Vue from "vue";
      export default {
        name: "PluginDemo",
        created() {
          console.log(this.$xx);
          console.log(Vue.xx);
          console.log(this.$num);
        },
      };
    </script>
  • plugins 文件夹下 index.js 文件

    import Vue from "vue";
    // 制作一个插件
    const myPlugin = {
      install() {
        // 组件内全局的变量
        Vue.prototype.$num = 1000;
    
        // 组件的全局指令 v-focus
        Vue.directive("focus", {
          inserted: function (el, binding) {
            // binding是一个对象,binding.value代表所绑定的值
            binding.value && el.focus();
          },
        });
      },
    };
    // use方法主要就是执行了 参数内的 install 方法
    // install 就相当于是为 use 专门而生的
    Vue.use(myPlugin);
    // export default myPlugin;

# 混入

用的很少,但可能会用到。全局混入禁止使用
mixin 混入之后, 会自动和组件内的内容进行合并 ,有的合并有的会覆盖,组件本身优先级高,混入的优先执行

  • MixinDemo.vue

    <template>
      <div>111</div>
    </template>
    
    <script>
      import myMixin from "../mixins/index";
      export default {
        name: "MixinDemo",
        // 主要是看混入生命周期
        mixins: [myMixin],
        data() {
          return {
            num: 10,
          };
        },
        created() {
          console.log("组件自己的created");
          console.log(this.num); //10
        },
      };
    </script>
  • mixins 文件夹下的 index.js

    // 设置混入对象 就是组件内的一部分内容
    const myMixin = {
      created() {
        // this.hello();
        console.log("我是 minxin 内的声明周期 created");
      },
      data() {
        return {
          num: 100,
        };
      },
      /* methods: {
          hello: function() {
            console.log("hello from mixin!");
          },
        }, */
    };
    export default myMixin;

# 格式化文件

在根目录创建一个.prettierrc 文件

{
  "semi": false, //末尾是否要分号
  "singleQuote": true //用单引号代替双引号
}

# 跨域请求数据

  • index.js

    // 利用 node + express(依赖) 搭建简易服务器
    const express = require("express");
    // 利用 express 创建一个 app
    const app = express();
    // 设置一个端口 3000
    const port = 3000;
    
    // 只要是地址的协议,域名,端口号三者有一个不同就是跨域。
    
    //设置跨域访问
    app.all("*", (req, res, next) => {
      res.header("Access-Control-Allow-Origin", "*");
      res.header("Access-Control-Allow-Headers", "X-Requested-With");
      res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
      res.header("Content-Type", "application/json;charset=utf-8");
      next();
    });
    
    // 后台反馈的权限菜单数组
    const authMenu = [
      { pid: -1, name: "购物车", id: 1, auth: "cart" },
      { pid: 1, name: "购物车列表", id: 4, auth: "cart-list" },
      { pid: 4, name: "彩票", id: 5, auth: "lottery" },
      { pid: 4, name: "商品", id: 6, auth: "product" },
      { pid: -1, name: "商店", id: 2, auth: "shop" },
      { pid: -1, name: "个人中心", id: 3, auth: "store" },
    ];
    
    // 设置一个路由,相当于定义一个接口,返回对应的数据
    // get 方法,地址是 localhost:3000,返回值是"Hello World!"
    app.get("/", (req, res) => {
      res.send(authMenu);
    });
    
    // post 方法,要设置接收参数的话,需要借助一个
    /* app.get("/authmenu", (req, res) => {
        res.send(authMenu);
      }); */
    
    // 在对应的端口启动服务器
    app.listen(port, () => {
      console.log(`Example app listening at http://localhost:${port}`);
    });
    
    // 用 node index.js 执行
  • getTree.js

    // 制作一个处理扁平化数组的函数,将其转换成树
    const getTree = (authMenus) => {
      const menu = [];
      const sourceMap = {};
      authMenus.forEach((m) => {
        m.children = [];
        sourceMap[m.id] = m;
        if (m.pid === -1) {
          // 根目录
          menu.push(m);
        } else {
          sourceMap[m.pid].children.push(m);
        }
      });
      return menu;
    };
    export { getTree };

# 权限菜单

$router.addRoute (‘父路由 name’, 要添加的对象) 添加一条新的路由规则作为现有路由的子路由
如果在组件中使用 路由实例的 addRoute 方法,动态添加路由不会生效,需要到路由导航守卫中添加

  • router 文件夹下 index.js

    import Vue from "vue";
    import VueRouter from "vue-router";
    Vue.use(VueRouter);
    
    // 因为权限问题,该项目的页面用户看到的是不同的,如何实现
    // 1.我们可以将所有页面全部写好,然后在需要权限的页面添加判断()
    // 2.根据权限动态制作路由,添加到vue的路由中
    // 提前写好所有的路由数组,根据权限筛选,然后添加到项目中
    
    // 方案二的实现
    // 创建一个完整的权限路由数组,加上完整的页面。供以后筛选使用
    export const authRoutes = [
      {
        name: "cart",
        path: "cart",
        component: () => import("../views/Cart"),
        children: [
          {
            name: "cartlist",
            path: "cartlist",
            component: () => import("../views/CartList"),
            children: [
              {
                name: "lottery",
                path: "lottery",
                component: () => import("../views/Lottery.vue"),
              },
              {
                name: "product",
                path: "product",
                component: () => import("../views/Product.vue"),
              },
            ],
          },
        ],
      },
      {
        name: "shop",
        path: "shop",
        component: () => import("../views/Shop.vue"),
      },
      {
        name: "store",
        path: "store",
        component: () => import("../views/Store.vue"),
      },
    ];
    
    export default router;

# 如何生成打包报告

打包时,为了直观地发现项目中存在的问题,可以在打包时生成报告。

  • vue-cli-service build --report

  • VueUI 中 - 控制台 - 分析面板


文章作者: Mrr-cxh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Mrr-cxh !
赏
 上一篇
Vue-Router Vue-Router
Vue Router 是 Vue.js (opens new window)官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:嵌套的路由/视图表,模块化的、基于组件的路由配置,路由参数、查询、通配符,基于 Vue.js 过渡系统的视图过渡效果……
2021-05-24
下一篇 
Vue 3.0 Vue 3.0
Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
2021-05-08
  目录