美文网首页vue
2019年底史上最全Vue框架整理从基础到实战(二)

2019年底史上最全Vue框架整理从基础到实战(二)

作者: 练识 | 来源:发表于2019-12-09 13:25 被阅读0次

    单文件组件

    在很多Vue项目中,我们使用 Vue.component 来定义全局组件,紧接着用 new Vue({ el: '#app '}) 在每个页面内指定一个容器元素。

    这种方式在很多中小规模的项目中运作的很好,在这些项目里 JavaScript 只被用来加强特定的视图。但当在更复杂的项目中,或者你的前端完全由 JavaScript 驱动的时候,下面这些缺点将变得非常明显:

    • 全局定义强制要求每个 component 中的命名不得重复
    • 字符串模板 缺乏语法高亮,在 HTML 有多行的时候,需要用到丑陋的 \
    • 不支持 CSS 意味着当 HTML 和 JavaScript 组件化时,CSS 明显被遗漏
    • 没有构建步骤 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用预处理器,如 Pug (formerly Jade) 和 Babel

    文件扩展名为 .vuesingle-file components(单文件组件) 为以上所有问题提供了解决方法,并且还可以使用 webpack 或 Browserify 等构建工具。

    这是一个文件名为 Hello.vue 的简单实例:

    现在我们获得

    在看完上文之后,建议使用官方提供的 Vue CLI 3脚手架来开发工具,只要遵循提示,就能很快地运行一个带有.vue组件,ES2015,webpack和热重载的Vue项目

    Vue CLI3

    基本配置

    • 安装Nodejs
      • 保证Node.js8.9或更高版本
      • 终端中输入node -v,保证已安装成功
    • 安装淘宝镜像源
      • npm install -g cnpm --registry=https://registry.npm.taobao.org
      • 以后的npm可以用cnpm代替
    • 安装Vue Cli3脚手架
      • cnpm install -g @vue/cli
    • 检查其版本是否正确
      • vue --version

    快速原型开发

    使用 vue servevue build 命令对单个 *.vue 文件进行快速原型开发,不过这需要先额外安装一个全局的扩展:

    npm install -g @vue/cli-service-global
    

    vue serve 的缺点就是它需要安装全局依赖,这使得它在不同机器上的一致性不能得到保证。因此这只适用于快速原型开发。

    需要的仅仅是一个 App.vue 文件:

    <template>
        <div>
            <h2>hello world 单页面组件</h2>
        </div>
    </template>
    <script>
    export default {
        
    }
    </script>
    <style>
        
    </style>
    

    然后在这个 App.vue 文件所在的目录下运行:

    vue serve
    

    启动效果:



    网页效果:


    但这种方式仅限于快速原型开发,终归揭底还是使用vue cli3来启动项目

    创建项目

    vue create mysite
    

    详细的看官网介绍

    购物车


    App.vue

    <ul>
        <li v-for="(item, index) in cartList" :key="index">
            <h3>{{item.title}}</h3>
            <p>¥{{item.price}}</p>
            <button @click='addCart(index)'>加购物车</button>
        </li>
    </ul>
    
    cartList: [
        {
            id:1,
            title:'web全栈开发',
            price:1999
        },
        {
            id: 2,
            title: 'python全栈开发',
            price: 2999
        }
    ],
    

    新建Cart.vue购物车组件

    <template>
        <div>
            <table border='1'>
                <tr>
                    <th>#</th>
                    <th>课程</th>
                    <th>单价</th>
                    <th>数量</th>
                    <th>价格</th>
                </tr>
                <tr v-for="(c, index) in cart" :key="c.id" :class='{active:c.active}'>
                    <td>
                        <input type="checkbox" v-model='c.active'>
                    </td>
                    <td>{{c.title}}</td>
                    <td>{{c.price}}</td>
                    <td>
                        <button @click='subtract(index)'>-</button>
                        {{c.count}}
                        <button @click='add(index)'>+</button>
                    </td>
                    <td>¥{{c.price*c.count}}</td>
                </tr>
                <tr>
                    <td></td>
                    <td colspan="2">{{activeCount}}/{{count}}</td>
                    <td colspan="2">{{total}}</td>
                </tr>
            </table>
        </div>
    </template>
    <script>
        export default {
            name: "Cart",
            props: ['name', 'cart'],
            methods: {
                subtract(i) {
                    let count = this.cart[i].count;
                    // if(count > 1){
                    //     this.cart[i].count-=1
                    // }else{
                    //     this.remove(i)
                    // }
                    count > 1 ? this.cart[i].count -= 1 : this.remove(i);
                },
                add(i) {
                    this.cart[i].count++;
                },
                remove(i) {
                    if (window.confirm('确定是否要删除')) {
                        this.cart.splice(i, 1);
                    }
                }
            },
            data() {
                return {}
            },
            created() {},
            computed: {
                activeCount() {
                    return this.cart.filter(v => v.active).length;
                },
                count() {
                    return this.cart.length;
                },
                total() {
                    // let num = 0;
                    // this.cart.forEach(c => {
                    //     if (c.active) {
                    //         num += c.price * c.count
                    //     }
                    // });
                    // return num;
                    return this.cart.reduce((sum, c) => {
                        if (c.active) {
                            sum += c.price * c.count
                        }
                        return sum;
                    }, 0)
                }
            },
    
        }
    </script>
    <style scoped>
        .active {
            color: red;
        }
    </style>
    

    mock数据


    简单的mock,使用自带的webpack-dev-server即可,新建vue.config.js扩展webpack设置

    webpack官网介绍

    module.exports = {
        configureWebpack:{
            devServer:{
                // mock数据模拟
                before(app,server){
                    app.get('/api/cartList',(req,res)=>{
                        res.json([
                            {
                                id:1,
                                title:'web全栈开发',
                                price:1999
                            },
                            {
                                id: 2,
                                title: 'web全栈开发',
                                price: 2999
                            }
                        ])
                    })
                }
            }
        }
    }
    

    访问http://localhost:8080/api/cartList 查看mock数据

    使用axios获取接口数据npm install axios -S

    created() {
        axios.get('/api/cartList').then(res=>{
            this.cartList = res.data
        })
    }
    

    使用ES7的async+await语法

    async created() {
        // try-catch解决async-awiat错误处理
        try {
            const { data } = await axios.get('/cartList')
            this.cartList = data;
    
        } catch (error) {
            console.log(error);
        }
    },
    

    数据持久化


    localstorage+vue监听器

    如果组件没有明显的父子关系,使用中央事件总线进行传递

    Vue每个实例都有订阅/发布模式的额实现,使用on和emit

    main.js

    Vue.prototype.$bus = new Vue();
    

    App.vue

    methods: {
        addCart(index) {
            const good = this.cartList[index];
            this.$bus.$emit('addGood',good);
        }
    
    }
    

    Cart.vue

    data() {
        return {
            cart:JSON.parse(localStorage.getItem('cart')) || []
        }
    },
    //数组和对象要深度监听
    watch: {
        cart: {
            handler(n, o) {
                const total = n.reduce((total, c) => {
                    total += c.count
                    return total;
                }, 0)
                localStorage.setItem('total', total);
                localStorage.setItem('cart', JSON.stringify(n));
                this.$bus.$emit('add', total);
            },
            deep: true
        }
    },
    created() {
        this.$bus.$on('addGood', good => {
            const ret = this.cart.find(v => v.id === good.id);
            if (ret) { //购物车已有数据
                ret.count += 1;
            } else {
                //购物车无数据
                this.cart.push({
                    ...good,
                    count: 1,
                    active: true
                })
            }
        })
    },
    

    更复杂的数据传递,可以使用vuex,后面课程会详细介绍

    组件深入


    组件分类


    • 通用组件
      • 基础组件,大部分UI都是这种组件,比如表单 布局 弹窗等
    • 业务组件
      • 与需求挂钩,会被复用,比如抽奖,摇一摇等
    • 页面组件
      • 每个页面都是一个组件v

    使用第三方组件


    比如vue最流行的element,就是典型的通用组件,执行npm install element-ui安装

    import Vue from 'vue';
    import ElementUI from 'element-ui';
    import 'element-ui/lib/theme-chalk/index.css';
    import App from './App.vue';
    
    Vue.use(ElementUI);
    
    new Vue({
      el: '#app',
      render: h => h(App)
    });
    

    在vue-cli中可以使用vue add element 安装

    安装之前注意提前提交当前工作内容,脚手架会覆盖若干文件


    发现项目发生了变化,打开App.vue,ctrl+z撤回

    此时可以在任意组件中使用<el-button>

    官网element-ui的通用组件,基本上都是复制粘贴使用,在这里就不一一赘述,后面项目中用到该库,咱们再一一去使用

    关于组件设计,最重要的还是自己去设计组件,现在我们模仿element-ui提供的表单组件,手写实现表单组件m-form

    先看一下element-ui的表单

    新建FormElement.vue

    <template>
      <div>
        <h3>element表单</h3>
        <el-form
          :model="ruleForm"
          status-icon
          :rules="rules"
          ref="ruleForm"
          label-width="100px"
          class="demo-ruleForm"
        >
          <el-form-item label="用户名" prop="name">
            <el-input type="text" v-model="ruleForm.name" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="确认密码" prop="pwd">
            <el-input type="password" v-model="ruleForm.pwd" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item>
            <el-button type="primary" @click="submitForm('ruleForm')">提交</el-button>
          </el-form-item>
        </el-form>
      </div>
    </template>
    
    <script>
    export default {
      name: "FormElement",
      data() {
          return {
              ruleForm: {
                  name:'',
                  pwd:''
              },
              rules:{
                  name:[
                      {required:true,message:'请输入名称'},
                      {min:6,max:10,message:'请输入6~10位用户名'}
                  ],
                  pwd:[{require:true,message:'请输入密码'}],
              }
          }
      },
      methods: {
          submitForm(name) {
              this.$refs[name].validate(valid=>{
                  console.log(valid);
                  if(valid){
                      alert('验证成功,可以提交')
                  }else{
                      alert('error 提交');
                      return false;
                  }
                  
              })
             
          }
      },
    };
    </script>
    

    在App.vue组件中导入该组件,挂载,使用

    组件设计


    表单组件,组件分层

    1. Form负责定义校验规则
    2. FormtItem负责显示错误信息
    3. Input负责数据双向绑定
    4. 使用provide和inject内部共享数据

    表单控件实现双向的数据绑定

    Input.vue

    <template>
      <div>
        <input :type="type" @input="handleInput" :value="inputVal">
      </div>
    </template>
    
    <script>
    export default {
      props: {
        value: {
          type: String,
          default: ""
        },
        type: {
          type: String,
          default: "text"
        }
      },
      data() {
        return {
          //单向数据流的原则:组件内不能修改props
          inputVal: this.value
        };
      },
      methods: {
        handleInput(e) {
          this.inputVal = e.target.value;
          // 通知父组件值的更新
          this.$emit("input", this.inputVal);
        }
      }
    };
    </script>
    
    <style scoped>
    </style>
    

    FormElement.vue

    如果不传type表示默认值,在Input.vue的props中有说明
    <m-input v-model="ruleForm.name"></m-input>
    <m-input v-model="ruleForm.name" type='password'></m-input>
    
    
    //数据
    data() {
        return {
          ruleForm: {
            name: "",
            pwd: ""
          },
          rules: {
            name: [
              { required: true, message: "请输入名称" },
              { min: 6, max: 10, message: "请输入6~10位用户名" }
            ],
            pwd: [{ require: true, message: "请输入密码" }]
          }
        };
      },
    
    
    FormItem

    1. 获取当前输入框的规则
    2. 如果输入框和rule不匹配 显示错误信息
    3. Input组件中用户输入内容时,通知FormItem做校验
    4. 使用async-validator做出校验

    FormItem.vue

    <template>
      <div>
        <label v-if="label">{{label}}</label>
        <slot></slot>
        <!-- 校验的错误信息 -->
        <p v-if="validateStatus=='error'" class="error">{{errorMessage}}</p>
      </div>
    </template>
    
    <script>
    import schema from "async-validator";
    export default {
      name: "FormItem",
      data() {
        return {
          validateStatus: "",
          errorMessage: ""
        };
      },
      props: {
        label: {
          type: String,
          default: ""
        },
        prop: {
          type: String
        }
      }
    };
    </script>
    
    <style scoped>
    .error {
      color: red;
    }
    </style>
    
    

    FormElement.vue

    <m-form-item label="用户名" prop="name">
        <m-input v-model="ruleForm.name"></m-input>
    </m-form-item>
    <m-form-item label="密码" prop="pwd">
        <m-input v-model="ruleForm.pwd" type="password"></m-input>
    </m-form-item>
    
    

    此时网页正常显示,但没有校验规则,添加校验规则

    思路:比如对用户名进行校验,用户输入的用户名必须是6~10位

    npm i asycn-validator -S
    
    

    Input.vue

    methods: {
        handleInput(e) {
          this.inputVal = e.target.value;
          //....
           //通知父组件校验,将输入框的值实时传进去
          this.$parent.$emit("validate", this.inputVal);
        }
    }
    
    

    FormItem.vue

    import schema from "async-validator";
    export default {
      name: "FormItem",
      data() {
        return {
          validateStatus: "",
          errorMessage: ""
        };
      },
      methods: {
        validate(value) {//value为当前输入框的值
            // 校验当前项:依赖async-validate
            let descriptor = {};
            descriptor[this.prop] = this.form.rules[this.prop];
            // const descriptor = { [this.prop]: this.form.rules[this.prop] };
            const validator = new schema(descriptor);
    
            let obj = {};
            obj[this.prop] = value;
            //  let obj = {[this.prop]:this.form.model[this.prop]};
            validator.validate(obj, errors => {
              if (errors) {
                this.validateStatus = "error";
                this.errorMessage = errors[0].message;
              } else {
                this.validateStatus = "";
                this.errorMessage = "";
              }
            });
          
        }
      },
      created() {
        //监听子组件Input的派发的validate事件
        this.$on("validate", this.validate);
      },
      //注入名字 获取父组件Form 此时Form我们还没创建
      inject: ["form"],
      props: {
        label: {
          type: String,
          default: ""
        },
        prop: {
          type: String
        }
      }
    };
    
    
    Form

    1. 声明props中获取数据模型(model)和检验规则(rules)
    2. 当FormItem组件挂载完成时,通知Form组件开始缓存需要校验的表单项
    3. 将缓存的表单项进行统一处理,如果有一个是错误,则返回false.(思路:使用promise.all()进行处理)
    4. 声明校验方法,供父级组件方法调用validate()方法

    Form.vue

    声明props中获取数据模型(model)和检验规则(rules)

    <template>
        <div>
            <slot></slot>
        </div>
    </template>
    
    <script>
        export default {
            name:'Form',
            //依赖 
            provide(){
                return {
                    // 将表单的实例传递给后代,在子组件中我们就可以获取this.form.rules和this.form.rules
                    form: this
                }
            },
            props:{
                model:{
                    type:Object,
                    required:true
                },
                rules:{
                    type:Object
                }
            },
    
        }
    </script>
    
    

    当FormItem组件挂载完成时,通知Form组件开始缓存需要校验的表单项

    FormItem.vue

    mounted() {
        //挂载到form上时,派发一个添加事件
        //必须做判断,因为Form组件的子组件可能不是FormItem
        if (this.prop) {
            //通知将表单项缓存
            this.$parent.$emit("formItemAdd", this);
        }
    }
    
    

    Form.vue

    created () {
        // 缓存需要校验的表单项
        this.fileds = []
        this.$on('formItemAdd',(item)=>{
            this.fileds.push(item);
        })
    },
    
    

    将缓存的表单项进行统一处理,如果有一个是错误,则返回false.(思路:使用Promise.all()进行处理).

    注意:因为Promise.all方法的第一个参数是数组对象,该数组对象保存多个promise对象,所以要对FormItem的validate方法进行改造

    FormItem.vue

    validate() {
        // 校验当前项:依赖async-validate
        return new Promise(resolve => {
            const descriptor = { [this.prop]: this.form.rules[this.prop] };
            const validator = new schema(descriptor);
            validator.validate({[this.prop]:this.form.model[this.prop]}, errors => {
                if (errors) {
                    this.validateStatus = "error";
                    this.errorMessage = errors[0].message;
                    resolve(false);
                } else {
                    this.validateStatus = "";
                    this.errorMessage = "";
                    resolve(true);
                }
            });
        });
        
    }
    
    

    Form.vue

    methods: {
        validate(callback) {
            // 获取所有的验证结果统一处理 只要有一个失败就失败,
            // 将formItem的validate方法 验证修改为promise对象,并且保存验证之后的布尔值
            // tasks保存着验证之后的多个promise对象
            const tasks = this.fileds.map(item=>item.validate());
            let ret = true;
            // 统一处理多个promise对象来验证,只要有一个错误,就返回false,
            Promise.all(tasks).then(results=>{
                results.forEach(valid=>{
                    if(!valid){
                        ret = false;
                    }
                })
                callback(ret);
            }) 
        }
    },
    
    

    测试:

    <m-form :model="ruleForm" :rules="rules" ref="ruleForm2">
        <m-form-item label="用户名" prop="name">
            <m-input v-model="ruleForm.name"></m-input>
        </m-form-item>
        <m-form-item label="密码" prop="pwd">
            <m-input v-model="ruleForm.pwd" type="password"></m-input>
        </m-form-item>
        <m-form-item>
            <m-button type="danger" @click="submitForm2('ruleForm2')">提交</m-button>       
        </m-form-item>
    </m-form>
    
    
    methods:{
        submitForm2(name) {
            this.$refs[name].validate(valid=>{
                console.log(valid);
                if(valid){
                    alert('验证成功');
                }else{
                    alert('验证失败')
                }
            });
        }
    }
    
    

    最后

    如果想要更详细全面的资料,欢迎添加小姐姐的微信。还可以让她拉你入群,群里大牛学霸具在,即使做个安静的潜水鱼都可以受益匪浅。更有甜美的小姐姐管理员在线撩汉哦!

    本文由练识课堂发布!

    相关文章

      网友评论

        本文标题:2019年底史上最全Vue框架整理从基础到实战(二)

        本文链接:https://www.haomeiwen.com/subject/aoubgctx.html