20230701----重返学习-Vue的单向数据流-todoList项目-组件封装-jsx语法

45 篇文章 0 订阅
37 篇文章 0 订阅

day-103-one-hundred-and-three-20230701-Vue的单向数据流-todoList项目-组件封装-jsx语法

常见面试题

  • 面试题:怎样理解 Vue 的单向数据流?
  • 面试题:父组件可以监听到子组件的生命周期吗?
  • 面试题:vue中组件和插件有什么区别?
  • 面试题:平时开发中,你有没有封装过公共组件?如果封装过,则简单说一下你当时是怎么考虑的!

Vue的单向数据流

  • 面试题:怎样理解 Vue 的单向数据流?
    • 所谓单向数据流,指的是属性传递是单向的。
      • 父组件可以基于属性把信息传递给子组件 <Child :x="10" title="...">
      • 但是反过来,子组件是无法基于属性把信息传递给父组件的。
    • 我个人理解的单向数据流,应该还包含:父子组件的钩子函数触发时机,也是遵循单向数据深度优先原则的。
      • 第一次渲染:

        • 完整流程:父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 父组件开始渲染DOM --> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件开始渲染DOM --> 子组件结束渲染DOM --> 子组件mounted --> 父组件结束渲染DOM --> 父组件mounted
        • 具体步骤-根据组件中的钩子函数:
          1. 父组件渲染前期:父组件beforeCreate --> 父组件created --> 父组件beforeMount --> 父组件开始渲染DOM -> 下一阶段。
          2. 子组件渲染阶段:–> 子组件beforeCreate --> 子组件created --> 子组件beforeMount --> 子组件开始渲染DOM --> 子组件结束渲染DOM --> 子组件mounted -> 下一阶段。
          3. 父组件渲染后期: --> 父组件结束渲染DOM --> 父组件mounted
      • 组件更新:

        • 完整流程:父组件beforeUpdate -> 父组件开始更新 -> 子组件beforeUpdate -> 子组件开始更新 -> 子组件结束更新 -> 子组件updated -> 父组件结束更新 -> 父组件updated
        • 具体步骤-根据组件中的钩子函数:
          1. 父组件更新前期:父组件beforeUpdate -> 父组件开始更新 -> 下一阶段。
          2. 子组件更新阶段: -> 子组件beforeUpdate -> 子组件开始更新 -> 子组件结束更新 -> 子组件updated -> 下一阶段。
          3. 父组件更新后期: -> 父组件结束更新 -> 父组件updated
      • 组件销毁:

深度优先和广度优先
  • 深度优先和广度优先

    let obj = {
      x: 10,
      y: {
        z: 20,
        n: {
          m: 30,
        },
        k: 50,
      },
      h: 40,
    };
    
    • 深度优先

      let obj = {
        x: 10,
        y: {
          z: 20,
          n: {
            m: 30,
          },
          k: 50,
        },
        h: 40,
      };
      // x --> y --> z --> n --> m --> k --> h
      // x --> y --> y是对象,进入y --> y.z --> y.n --> y.n是对象,进入y.n --> y.n.m --> y.n对象结束,跳出y.n --> k --> y对象结束,跳出y --> h
      // x --> y --> y.z --> y.n --> y.n.m --> y.k --> h
      
    • 广度优先

      let obj = {
        x: 10,
        y: {
          z: 20,
          n: {
            m: 30,
          },
          k: 50,
        },
        h: 40,
      };
      // x --> y --> h --> z --> n --> k --> m
      // 第一层:[x --> y --> h] --> 第二层:[z --> n --> k] --> 第三层:[m]
      // x --> y --> h --> y.z --> y.n --> y.k --> y.n.m
      

父组件与子组件生命周期

  • 面试题:父组件可以监听到子组件的生命周期吗?
    • 父组件想监测到子组件的钩子函数触发,大体上有两种方案:
      1. 发布订阅:

        • 父组件向子组件事件池中注入自定义事件。
        • 子组件在指定的钩子函数触发时,通知自定义事件执行即可。
        • 代码示例:
          • fang/f20230701/day0701/src/views/Parent.vue父组件:

              <Child @md="childMounted" />
              methods: {
                childMounted() {
                  console.log(`子组件第一次渲染完毕了`);
                },
              },
            
            <template>
              <div class="parent-box">
                <Child @md="childMounted" />
              </div>
            </template>
            
            <script>
            import Child from "./Child.vue";
            export default {
              components: {
                Child,
              },
              methods: {
                childMounted() {
                  console.log(`子组件第一次渲染完毕了`);
                },
              },
            };
            </script>
            
            <style lang="less" scoped>
            .parent-box {
              box-sizing: border-box;
              position: relative;
              margin: 20px auto;
              width: 200px;
              height: 200px;
              background: lightblue;
            }
            </style>
            
          • fang/f20230701/day0701/src/views/Child.vue子组件:

              mounted() {
                this.$emit("md");
              },
            
            <template>
              <div class="child-box"></div>
            </template>
            
            <script>
            export default {
              mounted() {
                this.$emit("md");
              },
            };
            </script>
            
            <style lang="less" scoped>
            .child-box {
              box-sizing: border-box;
              position: absolute;
              top: 50%;
              left: 50%;
              transform: translate(-50%, -50%);
              width: 100px;
              height: 100px;
              background: lightcoral;
            }
            </style>
            
          • 支持传递实参:

            • 代码示例:
              • fang/f20230701/day0701/src/views/Parent.vue父组件:

                <Child @md="childMounted" />
                methods: {
                  childMounted(childBtn) {
                      console.log('子组件第一次渲染完毕了',childBtn)
                  }
                }
                
                <template>
                  <div class="parent-box">
                    <Child @md="childMounted" />
                    <!-- <Child @hook:mounted="childMounted" /> -->
                  </div>
                </template>
                
                <script>
                import Child from "./Child.vue";
                export default {
                  components: {
                    Child,
                  },
                  methods: {
                    childMounted(...params) {
                      console.log(`子组件第一次渲染完毕`,params);
                    },
                  },
                };
                </script>
                
              • fang/f20230701/day0701/src/views/Child.vue子组件:

                mounted() {
                  this.$emit('md',this.$refs.btn)
                }
                
                <template>
                  <div class="child-box">
                    <button ref="btn">子组件按钮</button>
                  </div>
                </template>
                
                <script>
                export default {
                  mounted() {
                    this.$emit("md",this.$refs.btn);
                  },
                };
                </script>
                
      2. 直接基于@hook:钩子函数监听即可。

        <Child @hook:mounted="childMounted" />
        
        • 代码示例:

          • 父组件:

            <Child @hook:mounted="childMounted" />
            methods: {
              childMounted() {
                console.log(`子组件第一次渲染完毕`);
              },
            }
            
            <template>
              <div class="parent-box">
                <Child @hook:mounted="childMounted" />
              </div>
            </template>
            
            <script>
            import Child from "./Child.vue";
            export default {
              components: {
                Child,
              },
              methods: {
                childMounted(...params) {
                  console.log(`子组件第一次渲染完毕`,params);
                },
              },
            };
            </script>
            
          • 子组件:

            <template>
              <div class="child-box">
                <button ref="btn">子组件按钮</button>
              </div>
            </template>
            
            <script>
            export default {
            };
            </script>
            
          • 不支持传递实参:

            • 父组件:

              <Child @hook:mounted="childMounted" />
              methods: {
                childMounted(...params) {
                  console.log(`子组件第一次渲染完毕`,params);//`子组件第一次渲染完毕` [];
                },
              }
              
        • 虽然第二种方式比较简单,但是第一种发布订阅的模式,其支持:给父组件的方法传递实参(可以是子组件中的一些内容),所以平时开发中,需要传参则采用第一种,不需要则采用第二种即可。

todoList项目

  • 项目创建:
  • 项目代码:
    • fang/f20230701/day0701/src/views/TodoList.vue父组件

      <template>
        <div class="todo-box">
          <div class="handle">
            <el-input placeholder="请输入任务描述" v-model.trim="text" />
            <el-button type="primary" @click="submit()">新建任务</el-button>
          </div>
          <list-item
            v-for="item in list"
            :key="item.id"
            :info="item"
            @handle="handle"
          />
          <!-- <list-item />
          <list-item />
          <list-item /> -->
        </div>
      </template>
      
      <script>
      // 全局引入了element-ui,而this.$message是element-ui导入并注册的。
      import ListItem from "../components/ListItem.vue";
      import _ from "@/assets/utils";
      /* _.storage为下方代码:
      // 具备有效期的LocalStorage存储
      const storage = {
        set(key, value) {
          localStorage.setItem(
            key,
            JSON.stringify({
              time: +new Date(),
              value,
            })
          );
        },
        get(key, cycle = 2592000000) {
          cycle = +cycle;
          if (isNaN(cycle)) cycle = 2592000000;
          let data = localStorage.getItem(key);
          if (!data) return null;
          let { time, value } = JSON.parse(data);
          if (+new Date() - time > cycle) {
            storage.remove(key);
            return null;
          }
          return value;
        },
        remove(key) {
          localStorage.removeItem(key);
        },
      }; */
      export default {
        components: {
          ListItem,
        },
        data() {
          // 组件第一次渲染:先从本地中获取已有的任务列表。
          let cache = _.storage.get("TODO_CACHE");
          return {
            //任务列表;
            list: cache || [],
            //任务框中输入的内容。
            text: "",
          };
        },
        methods: {
          submit() {
            // 验证text是否为空。
            if (this.text.length === 0) {
              this.$message.warning(`任务描述不可为空哦~`);
              return;
            }
      
            // 新增任务。
            this.list.push({
              id: +new Date(),
              text: this.text,
            });
            this.text = "";
          },
          // 修改或删除任务。
          handle(type, id, text) {
            // type:操作类型 delete/update
            // id:要删除/修改任务项的编号。
            // text:如果是修改操作,text存储的是要修改的信息。
            if (type === "delete") {
              this.list = this.list.filter((item) => {
                return +item.id !== +id;
              });
              return;
            }
      
            if (type === "update") {
              this.list = this.list.map((item) => {
                if (+item.id === +id) {
                  item.text = text;
                }
                return item;
              });
            }
          },
        },
      
        // 监听任务列表的变化,把最新的信息存储到本地。
        watch: {
          list: {
            deep: true,
            handler() {
              _.storage.set("TODO_CACHE", this.list);
            },
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .todo-box {
        box-sizing: border-box;
        margin: 50px auto;
        width: 400px;
      
        .handle {
          padding-bottom: 20px;
          border-bottom: 1px dashed #ddd;
          display: flex;
          justify-content: space-between;
          align-items: center;
      
          .el-button {
            margin-left: 20px;
          }
        }
      }
      </style>
      
    • fang/f20230701/day0701/src/components/ListItem.vue子组件

      <template>
        <div class="item-box" v-if="info">
          <div class="content">
            <el-input size="mini" v-if="isUpdate" v-model="copyText" />
            <span class="textCon" v-else>{{ copyText }}</span>
          </div>
          <div class="handle">
            <el-popconfirm title="您确定要删除本条任务吗?" @confirm="removeHandle">
              <el-button type="danger" size="mini" slot="reference">删除</el-button>
            </el-popconfirm>
            <el-button
              type="success"
              size="mini"
              v-if="!isUpdate"
              @click="triggerUpdate"
            >
              修改
            </el-button>
            <template v-else>
              <el-button type="success" size="mini" @click="saveUpdate">
                保存
              </el-button>
              <el-button type="info" size="mini" @click="cancelUpdate">
                取消
              </el-button>
            </template>
          </div>
        </div>
      </template>
      
      <script>
      export default {
        // 注册接收属性。
        props: {
          info: {
            type: Object,
            required: true,
          },
        },
        // 定义状态;
        data() {
          return {
            isUpdate: false,
            copyText: this.info.text,
          };
        },
        //定义操作的方法:
        methods: {
          //删除任务。
          removeHandle() {
            // 把父组件中存在的某条任务删除。
            this.$emit("handle", "delete", this.info.id);
          },
          // 触发修改操作。
          triggerUpdate() {
            this.isUpdate = true;
          },
          // 保存修改的信息。
          saveUpdate() {
            if (this.copyText.length === 0) {
              this.$message.warning(`任务描述不能为空哦~`);
              return;
            }
      
            // 把父组件中存在的某条任务进行修改。
            this.$emit("handle", "update", this.info.id, this.copyText);
            this.isUpdate = false;
          },
          // 取消修改操作。
          cancelUpdate() {
            this.isUpdate = false;
            this.copyText = this.info.text;
          },
        },
      };
      </script>
      
      <style lang="less" scoped>
      .item-box {
        margin: 15px 0;
      
        .content {
          margin-bottom: 5px;
      
          .textCon {
            line-height: 30px;
            font-size: 14px;
          }
      
          .el-input {
            width: 200px;
          }
        }
      
        .handle {
          .el-button {
            margin-right: 10px;
            margin-left: 0;
          }
        }
      }
      </style>
      

组件封装

  • 在组件化开发的模式下,有一个非常重要的知识:如何抽离封装通用的组件!
  • 一般我们封装的组件,按照特点可以分为:
    • 业务组件-针对于特定的项目,包含一定的业务逻辑:
      • 普通业务组件:
        • 在SPA单页面应用中,每一个路由页面都是一个组件。
        • 一个页面内容比较多,我们开发的时候,把其拆分成多个组件-这些组件可能没有复用性,最后合并渲染。
      • 通用业务组件:
        • 封装的组件会在很多地方用到(比如:推荐列表、新闻列表、回退按钮…)
    • 功能组件-不单纯针对某一个项目,而是适用于很多项目:
      • UI组件库中提供的组件都是功能组件。
        • 我们平时开发的时候,会结合当下的业务需求,对这些组件进行二次封装。
          • 例如:button组件设置loading防抖效果(比如点击事件执行时,自动有loading效果)、Table表格+筛选或分页等的二次封装、骨架屏的二次封装(样式修改及结构简化)。
      • 我们还会自己封装一些UI组件库不具备的组件或者使用第三方插件。
        • 例如:大文件切片上传和断点续传、pdf或word或excel的预览、富文本编辑器、复杂的轮播图效果!
  • 但是不论封装什么类型的组件,最核心的思想:让组件具备更强的复用性,支持更多效果的实现!
    • 首先,我们要改变思想观念:开发项目之前,首先分析那些东西是有类似的部分,需要进行封装提取的!
      • 可能是把几个组件合并在一起,变为一个完整的通用组件。
      • 也可能仅仅是调整一些样式,变为和项目风格统一的效果。
      • 还可能是在原有组件的基础上,扩充一些单独的功能。
      • 当然最主要的还是:包含结构、样式、功能,并在别人使用的时候可以通过传递不同的信息,实现不同的效果。
    • 如何让组件具备更强的复用性:
      • 基于:属性、插槽、自定义事件、实例(拿到组件实例,就可以调用实例上暴露的方法)。
      • 多参考相似的案例需求,进行归纳总结,在封装的时候,让其具备更多的不确定性。
        • 更多的不确定性也就是更多的各种合理属性和插槽,用户可以选择一些属性来定制的不同的效果。
  • 我们封装的组件,有不同的调用方式:
    • 直接在视图中调用渲染 <el-button></el-button>;
      • 封装组件;
      • 基于Vue.component()注册为全局组件;
    • 基于某些方法的执行进行渲染 this.$message.success('...');
      • 封装组件;
      • 基于Vue.extend()处理。
  • 封装组件的时候,我们基本上都使用<template>语法来构建视图,但是其具备弱编程性-即不灵活,此时我们可以基于强编程性jsx语法,来替代<template>语法

封装公共组件

  • 面试题:平时开发中,你有没有封装过公共组件?如果封装过,则简单说一下你当时是怎么考虑的!
    • 自己思考。

代码片断

封装loading防抖按钮

  • 参考来源:

  • 未封装前:

    • fang/f20230701/day0701/src/views/Demo1.vue

      <template>
        <div class="demo-box">
          <el-button type="danger" :loading="deleteLoading" @click="handleDelete"
            >
            删除
            </el-button
          >
          <el-button type="primary" :loading="updateLoading" @click="handleUpdate">修改</el-button>
        </div>
      </template>
      
      <script>
      /* this.$API.query为
      const query = (interval = 1000) => {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve({
              code: 0,
              message: "ok",
            });
          }, interval);
        });
      }; */
      export default {
        name: "Demo",
        data() {
          return {
            deleteLoading: false,
            updateLoading: false,
          };
        },
        methods: {
          async handleDelete() {
            this.deleteLoading = true;
            try {
              let { code } = await this.$API.query(2000);
              if(code===0){
                this.$message.success(`恭喜你,删除成功!`)
              }else{
                this.$message.error(`删除失败,请稍后再试!`)
              }
            } catch (error) {
              console.log(`error:-->`, error);
            }
            this.deleteLoading = false;
          },
          async handleUpdate(){
            this.updateLoading = true;
            try {
              let { code } = await this.$API.query(2000);
              if(code===0){
                this.$message.success(`恭喜你,修改成功!`)
              }else{
                this.$message.error(`修改失败,请稍后再试!`)
              }
            } catch (error) {
              console.log(`error:-->`, error);
            }
            this.updateLoading = false;
          }
        },
      };
      </script>
      
      <style lang="less" scoped>
      .demo-box {
        box-sizing: border-box;
        margin: 20px auto;
        padding: 20px;
        width: 200px;
        border: 1px solid lightcoral;
      
        .el-button {
          display: block;
          margin-bottom: 20px;
          margin-left: 0;
        }
      }
      </style>
      
  • 简单的封装:

    1. 创建一个组件:

      • fang/f20230701/day0701/src/components/ButtonAgain.vue
        • ButtonAgain组件:
          • 使用方式需要和ElButton保持一致。
          • 只不过loading效果,组件内部处理好即可!
        • 别人的代码:Vue2进阶/day0701/src/components/ButtonAgainTemplate.vue
    2. 在入口文件处引入,并全局注册:

      • fang/f20230701/day0701/src/main.js或fang/f20230701/day0701/src/global.js,因为global.js是在入口文件main.js直接引入的,和在入口文件执行代码差不多。

        import ButtonAgain from "./components/ButtonAgain.vue";
        Vue.component(ButtonAgain.name, ButtonAgain);
        
    3. 在需要用到该按钮的地方直接使用。

      <template>
        <button-again type="danger" plain size="small" @click="handleDelete" ref="AA">
          删除
        </button-again>
      </template>
      
      • fang/f20230701/day0701/src/views/Demo2.vue

        <template>
          <div class="demo-box">
            <button-again type="danger" plain size="small" @click="handleDelete" ref="AA">
              删除
            </button-again>
            <button-again type="primary" circle icon="el-icon-edit" @click="handleUpdate">
              
            </button-again>
          </div>
        </template>
        
        <script>
        /* //this.$API.query为:
          const query = (interval = 1000) => {
            return new Promise((resolve, reject) => {
              setTimeout(() => {
                resolve({
                  code: 0,
                  message: "ok",
                });
              }, interval);
            });
          };
        */
        export default {
          name: "Demo",
          methods: {
            async handleDelete() {
              try {
                let { code } = await this.$API.query(2000);
                if (code === 0) {
                  this.$message.success(`恭喜你,删除成功!`);
                } else {
                  this.$message.error(`删除失败,请稍后再试!`);
                }
              } catch (error) {
                console.log(`error:-->`, error);
              }
            },
            async handleUpdate() {
              try {
                let { code } = await this.$API.query();
                if (code === 0) {
                  this.$message.success(`恭喜你,修改成功!`);
                } else {
                  this.$message.error(`修改失败,请稍后再试!`);
                }
              } catch (error) {
                console.log(`error:-->`, error);
              }
            },
          },
          mounted () {
            console.log(`this.$refs.AA-->`, this.$refs.AA);
            
          }
        };
        </script>
        
        <style lang="less" scoped>
        .demo-box {
          box-sizing: border-box;
          margin: 20px auto;
          padding: 20px;
          width: 200px;
          border: 1px solid lightcoral;
        
          .el-button {
            display: block;
            margin-bottom: 20px;
            margin-left: 0;
          }
        }
        </style>
        
    • 处理思路步骤:
  • 封装重构-jsx语法:

    1. 创建一个组件:

      • fang/f20230701/day0701/src/components/ButtonAgain.vue

        <script>
        export default {
          name: "ButtonAgain",
          inheritAttrs: false,
          data() {
            return {
              loading: false,
            };
          },
          methods: {
            async handle(ev) {
              this.loading = true;
              try {
                await this.$listeners.click(ev);
              } catch (err) {
                console.log("ButtonAgain Error:", err.message);
              }
              this.loading = false;
            },
          },
          mounted() {
            this.ElButtonIns = this.$refs.child;
          },
          render() {
            // 传递属性的筛选
            let attrs = {},
              area = [
                "type",
                "size",
                "icon",
                "nativeType",
                "disabled",
                "plain",
                "autofocus",
                "round",
                "circle",
              ];
            Object.keys(this.$attrs).forEach((key) => {
              if (!area.includes(key)) return;
              attrs[key] = this.$attrs[key];
            });
        
            return (
              <el-button
                {...{ attrs }}
                loading={this.loading}
                vOn:click={this.handle}
                ref="child"
              >
                {this.$slots.default}
              </el-button>
            );
          },
        };
        </script>
        
        <style lang="less" scoped></style>
        
    2. 在入口文件处引入,并全局注册:

      • fang/f20230701/day0701/src/main.js或fang/f20230701/day0701/src/global.js,因为global.js是在入口文件main.js直接引入的,和在入口文件执行代码差不多。

        import ButtonAgain from "./components/ButtonAgain.vue";
        Vue.component(ButtonAgain.name, ButtonAgain);
        
    3. 在需要用到该按钮的地方直接使用。

      <template>
        <button-again type="danger" plain size="small" @click="handleDelete" ref="AA">
          删除
        </button-again>
      </template>
      
      • fang/f20230701/day0701/src/views/Demo2.vue

        <template>
          <div class="demo-box">
            <button-again
              type="danger"
              plain
              size="small"
              @click="handleDelete"
              ref="AA"
            >
              删除
            </button-again>
        
            <button-again
              type="primary"
              circle
              icon="el-icon-edit"
              @click="handleUpdate"
            >
            </button-again>
          </div>
        </template>
        
        <script>
        export default {
          name: "Demo",
          methods: {
            async handleDelete() {
              try {
                let { code } = await this.$API.query(2000);
                if (+code === 0) {
                  this.$message.success("恭喜您,删除成功!");
                } else {
                  this.$message.error("删除失败,请稍后再试!");
                }
              } catch (_) {}
            },
            async handleUpdate() {
              try {
                let { code } = await this.$API.query();
                if (+code === 0) {
                  this.$message.success("恭喜您,修改成功!");
                } else {
                  this.$message.error("修改失败,请稍后再试!");
                }
              } catch (_) {}
            },
          },
          mounted() {
            console.log(this.$refs.AA);
          },
        };
        </script>
        
        <style lang="less" scoped>
        .demo-box {
          box-sizing: border-box;
          margin: 20px auto;
          padding: 20px;
          width: 200px;
          border: 1px solid lightcoral;
        
          .el-button {
            display: block;
            margin-bottom: 20px;
            margin-left: 0;
          }
        }
        </style>
        

对于一个组件

  • 对于一个组件:
    • 从技术角度来讲。
      1. 调用方式来决定是jsx还是template语法。
      2. 决定属性、自定义事件、
    • 从思维角度上:
      1. 观察通用性,查看需求及相似的例子。
      2. 普通业务组件、通用业务组件、UI组件库二次封装、第三方插件。

jsx语法

  • 纯h函数创建:

    <script>
    export default {
      name: "Demo",
      data() {
        return {
          title: "Vue视图构建语法",
          level: 1,
        };
      },
      /*
        https://v2.cn.vuejs.org/v2/guide/render-function.html
        render函数:基于JSX语法构建视图 
          + h:createElement
        */
      render(h) {
        return h(
          `h${this.level}`,
          {
            style: {
              color: "red",
            },
          },
          [
            this.title,
            h("span", {}, [100]),
            h(
              "el-button",
              {
                props: {
                  type: "primary",
                },
              },
              ["哈哈"]
            ),
          ]
        );
      },
    };
    </script>
    
  • jsx语法与h函数:

    <script>
    export default {
      name: "Demo",
      data() {
        return {
          title: "Vue视图构建语法",
          level: 1,
        };
      },
      methods: {
        handle() {
          this.level = 2;
        },
      },
      render(h) {
        let styObj = {
          color: "red",
        };
        return h(`h${this.level}`, { style: styObj }, [
          this.title,
          // https://github.com/vuejs/jsx-vue2
          <span vOn:click={this.handle}>100</span>,
          <el-button type="primary">哈哈哈</el-button>,
        ]);
      },
    };
    </script>
    

进阶参考

  1. Button 按钮
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值