35

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

 4 years ago
source link: https://juejin.im/post/5dedbe0f518825124316825b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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



1

单文件组件

在很多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 的简单实例:

1

现在我们获得

在看完上文之后,建议使用官方提供的 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
复制代码

启动效果:

1

网页效果:

1

但这种方式仅限于快速原型开发,终归揭底还是使用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 安装

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

1

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

1

此时可以在任意组件中使用<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内部共享数据
1

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

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
    }
  }
};

复制代码

  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框架整理从基础到实战(一)

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

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

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

还有2件事拜托大家

一:求赞 求收藏 求分享 求留言,让更多的人看到这篇内容

二:欢迎添加我的个人微信

备注“资料”, 300多篇原创技术文章,海量的视频资料即可获得

备注“加群”,我会拉你进技术交流群,群里大牛学霸具在,哪怕您做个潜水鱼也会学到很多东西

1




About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK