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

NO IMAGE

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

單文件組件

在很多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 的簡單實例:

2020年史上最全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

啟動效果:

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

網頁效果:

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

但這種方式僅限於快速原型開發,終歸揭底還是使用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每個實例都有訂閱/發佈模式的額實現,使用2020年史上最全Vue框架整理從基礎到實戰(二)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 安裝

安裝之前注意提前提交當前工作內容,腳手架會覆蓋若干文件

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

發現項目發生了變化,打開App.vue,ctrl+z撤回

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

此時可以在任意組件中使用<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內部共享數據
2020年史上最全Vue框架整理從基礎到實戰(二)

表單控件實現雙向的數據綁定

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多篇原創技術文章,海量的視頻資料即可獲得

備註“加群”,我會拉你進技術交流群,群裡大牛學霸具在,哪怕您做個潛水魚也會學到很多東西

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

相關文章

flutter好用的輪子推薦二十flutter仿iPhone鎖屏界面

新的API可以讓網頁直接讀寫硬盤上的文件?

手摸手教你寫個ESLint插件以及瞭解ESLint的運行原理

深入理解flexgrow、flexshrink、flexbasis