2020年史上最全Vue框架整理從基礎到實戰(二)

單文件組件

在不少Vue項目中,咱們使用 Vue.component 來定義全局組件,緊接着用 new Vue({ el: '#app '}) 在每一個頁面內指定一個容器元素。css

這種方式在不少中小規模的項目中運做的很好,在這些項目裏 JavaScript 只被用來增強特定的視圖。但當在更復雜的項目中,或者你的前端徹底由 JavaScript 驅動的時候,下面這些缺點將變得很是明顯:html

  • 全局定義強制要求每一個 component 中的命名不得重複
  • 字符串模板 缺少語法高亮,在 HTML 有多行的時候,須要用到醜陋的 \
  • 不支持 CSS 意味着當 HTML 和 JavaScript 組件化時,CSS 明顯被遺漏
  • 沒有構建步驟 限制只能使用 HTML 和 ES5 JavaScript, 而不能使用預處理器,如 Pug (formerly Jade) 和 Babel

文件擴展名爲 .vuesingle-file components(單文件組件) 爲以上全部問題提供瞭解決方法,而且還可使用 webpack 或 Browserify 等構建工具。前端

這是一個文件名爲 Hello.vue 的簡單實例:vue

如今咱們得到node

在看完上文以後,建議使用官方提供的 Vue CLI 3腳手架來開發工具,只要遵循提示,就能很快地運行一個帶有.vue組件,ES2015,webpack和熱重載的Vue項目python

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 文件進行快速原型開發,不過這須要先額外安裝一個全局的擴展:webpack

npm install -g @vue/cli-service-global
複製代碼

vue serve 的缺點就是它須要安裝全局依賴,這使得它在不一樣機器上的一致性不能獲得保證。所以這隻適用於快速原型開發。ios

須要的僅僅是一個 App.vue 文件:git

<template>
    <div>
        <h2>hello world 單頁面組件</h2>
    </div>
</template>
<script>
export default {
    
}
</script>
<style>
    
</style>
複製代碼

而後在這個 App.vue 文件所在的目錄下運行:github

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框架整理從基礎到實戰(一)

2019年末史上最全Vue框架整理從基礎到實戰(三)

2019年末史上最全Vue框架整理從基礎到實戰(四)

2019年末史上最全Vue框架整理從基礎到實戰(五)

最後

還有2件事拜託你們

一:求贊 求收藏 求分享 求留言,讓更多的人看到這篇內容

二:歡迎添加個人我的微信

備註「資料」, 300多篇原創技術文章,海量的視頻資料便可得到

備註「加羣」,我會拉你進技術交流羣,羣裏大牛學霸具在,哪怕您作個潛水魚也會學到不少東西

相關文章
相關標籤/搜索