vue使用的血淚心得

vue.js使用的人愈來愈多了,大多數的公司也在使用vue框架進行開發本身公司的產品。雖然vue.js語法比較簡單,官網文檔也比較詳細,可是越簡單的東西,越容易讓人忽視細節,從而容易致使代碼水平良莠不齊,風格各不相同。 對於初學者,感受通常不怎麼會花時間去查看官網文檔,不知道把視點落在何處。只有往後本身上班的時候碰到問題,踩到坑,加過班,苦修過,纔有所領悟。 接下來,我將會對我這幾年使用vue的一些經驗心得進行總結,但願能幫助你們少碰一些壁,少踩一些坑。javascript

1.建立vue腳手架不等於建立vue項目

對於一些新手,在剛剛學習vue的時候,每一次新建vue項目,都會先執行建立vue腳手架命令。究其緣由,是由於他把腳手架以及vue項目混淆了,其實,腳手架就是咱們搭建vue項目的環境,咱們能夠把它看做是一個舞臺,一個舞臺能夠有n個節目在它上面表演。一樣道理,咱們只須要搭建一次腳手架就能夠了,日後,咱們只須要使用建立項目的代碼便可。css

2.插件安裝方式

在vue 裏面,但凡是插件,咱們均可以嘗試使用npm進行安裝,因此,有時候,對於新手,就不須要從網上下載,再解壓縮,而後再導入項目裏面去了.html

3.ESLint

新手在剛接觸vue的時候,不建議使用ESLint,由於有可能雖然你的代碼邏輯寫得很正確,可是有時忘記寫一個分號,程序就會出錯,這會給你學習vue會大受打擊。 當你可以完成一個小Demo的時候,你應該要開始重視ESLint了。在職場上,軟件開發是一個團隊進行,別人可能須要閱讀你的代碼,大的公司還須要代碼審覈,寫的很差也容易被別人吐槽。前端

組件命名規範

<!--好的命名,借鑑element,使用鏈接符鏈接起來,所有小寫-->
<el-form label-width="160px"></el-form>
<!--不推薦,過於簡單,沒有實際意義-->
<list></list>
複製代碼

組件配置項與事件命名

定義組件時使用駝峯: el-formvue

props:{
 labelWidth:{ // 使用駝峯
    type:String,
    default:()=>""
 }
},
methods:{
    click(){
     this.$emit("handle-click") // 所有小寫,使用「-」
    }
}
複製代碼

別人使用組件時屬性則對應使用 -,並所有小寫,這個是 Vue 自動處理的。而事件 @ 是不會轉換的,你 $emit 裏是什麼名字,則別人使用時也要這麼寫。java

<el-form label-width="160px"></el-form>
<!--曾經看到一個高級前端相似這樣命名,想打人-->
<el-form labelWidth="160px" @handleClick=""></el-form>
複製代碼

雖然很簡單和基礎,可是仍是有些人不屑於遵照。還有一些人對於 Vue 組件屬性加了 : 和沒加 : 仍是有點搞不清。 例如,沒有加 : 表示傳入給 current 的是一個字符串:node

<todo-list current="1"></todo-list>
複製代碼

加了 : 表示等號後面 "" 內是變量:webpack

<todo-list :current="current"></todo-list>
複製代碼
<todo-list :current="1"></todo-list>
複製代碼

若是熟悉這個就不會寫出如下代碼了:git

<todo-list :current="'1'"></todo-list>
<todo-list :current="quot;1quot;'"></todo-list>
複製代碼

保持合理的書寫順序

很多同窗對於 Vue 的生命週期和配置書寫沒有順序,喜歡把 compnents 選項放到最下面去,watch 和 computed 也寫到對象最下面,其實 Vue 頁面 methods 的代碼量最多。若是我想看這個頁面引用了哪些組件,須要翻動很多代碼,甚至可能別人不知道 components 寫在下面,他也想引入組件,就可能形成 compnents 選項重複。 書寫順序參考示例,猶如閱讀文章通常:es6

export default{
    name:"BaseButton",
    components:{

    },
    props:{

    },

    computed:{
    },

    data(){
     return {

     }
    },
    watch:{
    },
    mounted(){
    },
    methods:{
    }
}
複製代碼

上述問題若是配置了 ESLint,Vue-cli 都會自動幫助咱們進行提示和處理,強烈建議使用 Vue-cli 搭建項目時選上並開啓 ESLint。若是你先有項目沒有使用 ESLint 也沒有關係,只要到項目中安裝便可:

npm install eslint --save-dev
npm install babel-eslint --save-dev
npm install eslint-friendly-formatter --save-dev // 指定錯誤報告的格式規範插件
npm install eslint-loader --save-dev  // 啓動vuecli時就能夠檢測
npm install eslint-plugin-vue --save-dev  // 符合vue項目推薦的代碼風格
複製代碼

根目錄下添加驗證規則文件 .eslintrc.js:

module.exports = {
  root: true, // 根文件,不會往上一層查找
  parserOptions: {
    parser: 'babel-eslint',
    sourceType: 'module'
  },
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: ['plugin:vue/recommended', 'eslint:recommended'],

  // add your custom rules here
  //it is base on https://github.com/vuejs/eslint-config-vue
  rules: {
    "vue/max-attributes-per-line": [2, {
      "singleline": 10,
      "multiline": {
        "max": 1,
        "allowFirstLine": false
      }
    }],
    "vue/name-property-casing": ["error", "PascalCase"],
    'accessor-pairs': 2,
    'arrow-spacing': [2, {
      'before': true,
      'after': true
    }],
    'block-spacing': [2, 'always'],
    'brace-style': [2, '1tbs', {
      'allowSingleLine': true
    }],
    'camelcase': [0, {
      'properties': 'always'
    }],
    'comma-dangle': [2, 'never'],
    'comma-spacing': [2, {
      'before': false,
      'after': true
    }],
    'comma-style': [2, 'last'],
    'constructor-super': 2,
    'curly': [2, 'multi-line'],
    'dot-location': [2, 'property'],
    'eol-last': 2,
    'eqeqeq': [2, 'allow-null'],
    'generator-star-spacing': [2, {
      'before': true,
      'after': true
    }],
    'handle-callback-err': [2, '^(err|error)$'],
    'indent': [2, 2, {
      'SwitchCase': 1
    }],
    'jsx-quotes': [2, 'prefer-single'],
    'key-spacing': [2, {
      'beforeColon': false,
      'afterColon': true
    }],
    'keyword-spacing': [2, {
      'before': true,
      'after': true
    }],
    'new-cap': [2, {
      'newIsCap': true,
      'capIsNew': false
    }],
    'new-parens': 2,
    'no-array-constructor': 2,
    'no-caller': 2,
    'no-console': 'off',
    'no-class-assign': 2,
    'no-cond-assign': 2,
    'no-const-assign': 2,
    'no-control-regex': 0,
    'no-delete-var': 2,
    'no-dupe-args': 2,
    'no-dupe-class-members': 2,
    'no-dupe-keys': 2,
    'no-duplicate-case': 2,
    'no-empty-character-class': 2,
    'no-empty-pattern': 2,
    'no-eval': 2,
    'no-ex-assign': 2,
    'no-extend-native': 2,
    'no-extra-bind': 2,
    'no-extra-boolean-cast': 2,
    'no-extra-parens': [2, 'functions'],
    'no-fallthrough': 2,
    'no-floating-decimal': 2,
    'no-func-assign': 2,
    'no-implied-eval': 2,
    'no-inner-declarations': [2, 'functions'],
    'no-invalid-regexp': 2,
    'no-irregular-whitespace': 2,
    'no-iterator': 2,
    'no-label-var': 2,
    'no-labels': [2, {
      'allowLoop': false,
      'allowSwitch': false
    }],
    'no-lone-blocks': 2,
    'no-mixed-spaces-and-tabs': 2,
    'no-multi-spaces': 2,
    'no-multi-str': 2,
    'no-multiple-empty-lines': [2, {
      'max': 1
    }],
    'no-native-reassign': 2,
    'no-negated-in-lhs': 2,
    'no-new-object': 2,
    'no-new-require': 2,
    'no-new-symbol': 2,
    'no-new-wrappers': 2,
    'no-obj-calls': 2,
    'no-octal': 2,
    'no-octal-escape': 2,
    'no-path-concat': 2,
    'no-proto': 2,
    'no-redeclare': 2,
    'no-regex-spaces': 2,
    'no-return-assign': [2, 'except-parens'],
    'no-self-assign': 2,
    'no-self-compare': 2,
    'no-sequences': 2,
    'no-shadow-restricted-names': 2,
    'no-spaced-func': 2,
    'no-sparse-arrays': 2,
    'no-this-before-super': 2,
    'no-throw-literal': 2,
    'no-trailing-spaces': 2,
    'no-undef': 0,
    'no-undef-init': 2,
    'no-unexpected-multiline': 2,
    'no-unmodified-loop-condition': 2,
    'no-unneeded-ternary': [2, {
      'defaultAssignment': false
    }],
    'no-unreachable': 2,
    'no-unsafe-finally': 2,
    'no-unused-vars': 0,
    'no-useless-call': 2,
    'no-useless-computed-key': 2,
    'no-useless-constructor': 2,
    'no-useless-escape': 0,
    'no-whitespace-before-property': 2,
    'no-with': 2,
    'one-var': [2, {
      'initialized': 'never'
    }],
    'operator-linebreak': [2, 'after', {
      'overrides': {
        '?': 'before',
        ':': 'before'
      }
    }],
    'padded-blocks': [2, 'never'],
    'quotes': [2, 'single', {
      'avoidEscape': true,
      'allowTemplateLiterals': true
    }],
    'semi': [2, 'never'],
    'semi-spacing': [2, {
      'before': false,
      'after': true
    }],
    'space-before-blocks': [2, 'always'],
    'space-before-function-paren': [2, 'never'],
    'space-in-parens': [2, 'never'],
    'space-infix-ops': 2,
    'space-unary-ops': [2, {
      'words': true,
      'nonwords': false
    }],
    'spaced-comment': [2, 'always', {
      'markers': ['global', 'globals', 'eslint', 'eslint-disable', '*package', '!', ',']
    }],
    'template-curly-spacing': [2, 'never'],
    'use-isnan': 2,
    'valid-typeof': 2,
    'wrap-iife': [2, 'any'],
    'yield-star-spacing': [2, 'both'],
    'yoda': [2, 'never'],
    'prefer-const': 2,
    'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
    'object-curly-spacing': [2, 'always', {
      objectsInObjects: false
    }],
    'array-bracket-spacing': [2, 'never']
  }
}
複製代碼

更多配置還能夠去官網查看 ESLint。 再添加忽略檢驗文件 .eslintignore:

/build/
/config/
/cmas/
/node_modules/
/src/utils/
配置 webpack rules:
rules:[
 { // 加到最前面
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    enforce: 'pre',
    include: [resolve('src')],
    options: {
      formatter: require('eslint-friendly-formatter'),
      emitWarning: true
    }
  },
  ....
]
複製代碼

啓動項目便可啦!固然使用 VS Code 也能夠安裝插件 ESLint 和 Vetur,再配置下 setting.json 就能夠啦:

"eslint.autoFixOnSave": true,
 "eslint.validate": [
      "javascript",{
          "language": "vue",
          "autoFix": true
      },"html",
      "vue"
  ],
複製代碼

4.生命週期

created 與 mounted created 還未掛載到 DOM,不能訪問到 $el 屬性,可用於初始化一些數據,但和 DOM 操做相關的不能在 created 中執行;monuted 運行時,實例已經掛在到 DOM,此時能夠經過 DOM API 獲取到 DOM節點。因此若是界面須要計算一些頁面位置或者定位相關操做最好放到 mounted 內,而一些 API 的調用或者獲取 router 的 query 的操做能夠放到 created 內:

created(){
const userId = this.$route.query.id || ''
  API.getUserInfo(userId).then(()=>{
  // ...
  })
}
複製代碼

更好的建議將異步 API 放到 mounted 內,由於 created 內獲取數據過多容易致使系統白屏,對用戶體驗很差。 beforeDestory 這個鉤子也是常用的,通常用於組件銷燬前清除定時器,解綁事件監聽操做,從而防止切換到其餘頁面時定時器還在做用從而可能形成內存泄漏和性能問題。

beforeDestory(){
    if(this.timer){
        clearTimeInterval(this.timer)
        this.timer = null
    }
    window.removeEventListener('scroll', this.scrollhandle);
}
複製代碼

computed 與 watch

在 Vue 中咱們一般拿 computed 與 method 和 watch 進行對比,在開發過程當中不少人會對 computed 的使用陷入誤區。例如

computed:{
    typeClass(){
      return `my-button--${this.type}`
    },
    time(){ // 不必使用computed
      return Date.now()
    }
  },
複製代碼

computed 初衷是對響應式數據進行處理,例如上述例子中 type 改變,typeClass 值會自動更新,而第二個 time 內沒有任何響應式數據,這個時候就不必使用 computed 了。使用 computed 還有個好處就是它是依賴緩存的,只有 type 改變了值纔會從新計算,例如經典的購物車金額根據商品數量和價格自動更新就可使用 computed 來計算了,我推薦你們使用多使用 computed。 返回用戶數據中激活狀態的數據。

computed:{
    userIsActive(){
      return this.allUser.filter(item=>item.status===true)
    }
 }
複製代碼

再說說 watch,在開發經驗中,曾見過許多新手過分使用 watch,致使 watch 濫用,首先 watch 在性能上不如 computed,簡單數據監聽也還行,可是遇到大數量數據或者複雜數據,可能還須要深度監聽。 watch 有如下幾點常見應用: 輸入框實時查詢,有些搜索框不須要點擊來觸發查詢,而是搜索就會觸發,因而就只能監聽輸入值了。

watch: {
  inpVal:{
    handler: 'getList',
    immediate: true
  }
}
複製代碼

配合 v-model 使用:v-model 綁定的值通常爲雙向的,組件內部的 value 值改變了,須要實時 emit 給外部,例以下面組件例子,一個帶 v-model 的組件 search.vue:

<my-search v-model="searchValue"/>
複製代碼

組件內部實現:

export default {
  data(){
    inputVal:'',
  },
  props:{
    value:{
      type:String,
      default:()=>''
    }
  },
  watch:{
    value:function(newVal){ // 外部value改變實時跟新到inputVal
      this.inputVal = newVal
    },
    inputVal:function(newVal){ // 提交到外部
      this.$emit('input',newVal)
    }
  },
}
複製代碼

5樣式維護

Vue 中樣式的維護也是一個不可忽略的問題,常常會遇到如下兩個問題:

  • 本身寫的 CSS 與其餘的樣式相互衝突;
  • 想要修改第三方組件,例如 element-ui 組件樣式,發現修改不了。 對於第一個問題,若是使用了 Vue-cli 能夠在樣式 style 標籤上加上 scoped 屬性,Vue 中的 scoped 經過在 DOM 結構以及 CSS 樣式上加惟一不重複的標記:data-v-hash 的方式,以保證惟一(而這個工做是由 PostCSS 轉譯實現的),達到樣式私有化模塊化的目的。 例如你在組件文件寫了一個樣式:
.test{
 color:red
}
複製代碼

帶有 scoped 屬性會編譯爲:

.test [data-v-1a23ef] {
    color:red
}
複製代碼

這樣加個惟一的 hash 以及選擇器,就達到了這個樣式只能在當前組件內使用。 還有爲了不一些莫名其妙的樣式衝突,你們命名 CSS 時不要使用過於簡單的名字,從而防止與一些公共庫裏或者其餘人寫的樣式衝突,例以下面名字都不推薦:

.list
.header
.main
.title
.dialog
...
複製代碼

若是要修改一些公共組件的樣式,或者說強制須要修改帶有 scoped 屬性組件內的樣式,可使用 >>> 來修改,叫作樣式穿透。例如修改 element-ui 輸入框寬度:

.tolist >>> .el-input {
    width 240px
}
複製代碼

這樣 tolist 內的 el-input 寬度就爲 240px。 假如你須要使得系統全部的輸入框默認都是 240px 呢,在每一個文件內使用樣式穿透,顯然很差,這樣咱們每一個頁面都要寫一遍。咱們能夠單獨寫一個公共樣式文件,將其在 main.js 中引入便可。 common.css:

.el-input {
    width:240px;
    border-radius:0px;
}
複製代碼

main.js:

import '../styles/common.css 複製代碼

6.vue動畫

下面來講說關於 Vue 的動畫,雖然官網也有介紹動畫,可是不少同事並不會有意給系統加一些動態效果,若是你作的產品可以考慮到這些,無形之中就增強了交互效果,也讓別人對你另眼相看,這樣纔是專業的前端風範,而不是抱着功能實現了就行的態度。 下面以實現一個最簡單的左側面板呼出功能爲例。Vue 內置了組件 transition 來實現過渡效果。

<transition name="aside">
    <aside class="doc-aside" v-show="leftIsShow">
      123
    </aside>
 </transition>
複製代碼

在使用時給 transition 提供一個 name 選項,若是沒有提供則會默認一個「v」,而後書寫 Vue 提供的六個 class 便可

CSS:

.aside-enter
  width 0

.aside-enter-active
  transition width .5s

.aside-enter-to
  width 260px

.aside-leave
  width 260px

.aside-leave-active
  transition width .5s

.aside-leave-to
  width 0px
複製代碼

樣式的第一個橫槓 - 前必須對應 transition 中的 name。

7.組件

組件化也是 Vue 的一大核心,也是你們最常用的,可是不少細節仍是須要你們注意的。 數據流是單向的 這點尤爲重要,雖然官網反覆強調,可是仍是不少新手無心識地在子組件內修改了外部傳入進來的值。這裏有點須要說明,假如父組件傳入的是一個對象,若是子組件內修改這個對象的值,那個父組件也會對應修改,由於對象或者數組類型是引用類型,在內存中是同一份,可是不能理解爲通訊過程就是雙向了。若是值是數字或者布爾類型,那麼父組件修改會反饋到子組件,子組件修改卻不會反饋到父組件!!

<doc-aside 
     class="doc-aside" 
     v-show="leftIsShow"
     :data="asideData"
     :current="current">          
 </doc-aside>
複製代碼
data(){
    return {
      asideData:[
        {name:'jack',age:20},
        {name:'tom',age:21}
      ],
      current:1, // 子組件修改不會更新到父組件來的
    }
  },
複製代碼

因此子組件修改父組件最好仍是按照官網說的使用 $emit 方法,若是子組件非要修改值的話,能夠進行拷貝一份:

export default {
  props:{
    data:{
      type:Array,
      default:()=>[]
    },
  },
  data(){
    return {
      asideData:this.deepClone(this.data)
    }
  },
  methods:{
    deepClone(data){
      let obj = Array.isArray(data) ? []:{}
      for(const key in data){
        if(typeof data[key] === 'object'){
          obj[key] = this.deepClone(data[key])
        }else{
          obj[key] = data[key]
        }
      }
      return obj
    }
  }
}
複製代碼

data 初始化時會拷貝一份父組件數據,這樣父子之間就會隔開了,父組件再修改值也不會影響子組件了,子組件修改也不會影響父組件了,由於他們拷貝後內存地址不同。 其實這樣也很差,子組件通常要接收父組件修改從而實時反饋,這個時候咱們子組件內加個對 props 傳入的數據進行 watch,再從新賦值給 asideData:

watch:{
    data:function(newVal){
      this.asideData = this.deepClone(newVal)
    }
  },
複製代碼

固然不是任什麼時候候都須要這麼多步驟,假如子組件內只是展現數據,不會被用戶手動改動數據,就不須要進行各類 watch 和拷貝了,還可使用 computed 等。

props:{
  data:{
    type:Array,
    default:()=>[]
  }
},
computed:{
  userIsActive(){ //
    return this.data.filter(item=>item.status)
  }
}
複製代碼

建議父組件處理好數據格式再送入子組件,這樣子組件就不須要爲了單向流這麼麻煩來處理數據了

插槽

插槽使用是 Vue 組件開發的一種常見方式,使用插槽可讓別人使用組件時能夠自定義內容,而不是將組件寫死。見過很多同事,爲了應付需求一時爽,將佈局和數據格式都寫死了,這樣後期需求變了而致使加班。例如封裝一個簡單的按鈕組件。 方案一:

<el-button text="點擊"></el-button>
複製代碼

方案二:

<el-button >{{text}}</el-button>
複製代碼

顯然使用第二種方式好,第一種方式就只能傳入字符串了,第二種方式支持自定義,例如後期要在按鈕前加一個圖標:

<el-button >
    <i class="loading"/>
    {{text}}
</el-button>
複製代碼

而若是你熟悉 Vue API,大牛們通常是兩種方式都支持,既能夠傳入 text,還可使用插槽,若是有插槽則優先顯示插槽內容:

<button >
    <slot>
    {{text}}
    <slot>
</button>
複製代碼

若是外部沒有使用插槽 slot 就看成不存在,而直接顯示 text 內容。 做用域插槽:另外一個高級技巧就是做用域插槽,通常的插槽裏內容取的是父組件裏的 data。

<todo-list>
    <span slot="title">{{title}}<span>
</todo-list>
複製代碼

這個插槽裏 {{title}} 顯示的是父組件 data 裏的值,而做用域插槽呢,就是相反,能夠顯示子組件裏的變量。

<todo-list :data="listData">
</todo-list>
複製代碼

listData 多是這樣數據:

listData:[
    {id:0,title:'打籃球'},
    {id:1,title:'作菜'},
    {id:2,title:'敲代碼'}
 ]
複製代碼

todo-list:

{{item.id}} {{item.title}}

組件 todo-list 接收到這樣數據後須要佈局好,再使用 v-for 渲染出來便可。

這樣有個很差處,例如每一項只能定死了用 span 來渲染,並且 id 只能在 title 前面了,假如後期要加一些複雜的需求,例如 title 用 h2 標籤來顯示,或者加個其餘其餘標籤,這樣就又須要改組件了。因此咱們須要使用做用域將其暴露出去,可讓別人本身去玩,咱們只須要把數據告訴父組件就能夠。 只須要將數據賦在給 slot 組件上,slot 的屬性名能夠本身取,能夠叫 data 或者其餘名字。

<div v-for="(item,index) in listData" :key="index">
   <slot :data="item">
      <span>{{item.name}}</span>
   </slot>
 </div>
<todo-list :data="listData">
  <template slot-scope="{data}"> 
     {{data.name}}
   </template> 
</todo-list>
複製代碼

使用 slot-scope 能夠獲取內部 slot 屬性值 {data} 對應組件內部傳入的屬性名

8.數據綁定

使用 v-model 一些新手寫組件時老是習慣使用屬性名,而後再 @ 一個事件名稱。

<el-input :value="curVal" @input="updateValue"></el-input>
複製代碼
updateValue(val){
    this.curVal = val
}
複製代碼

這樣雖然實現了雙向綁定,可是別人卻要手動寫一個 方法 updateValue 來手動賦值給 currVal,其實若是組件的 props 裏定義了一個屬性名叫作 value,並 $emit (「input」),知足這兩個條件就可使用 v-model 了(必須爲 value 屬性和 input 事件名,名字不能隨便取,固然非要改要去經過 Vue 的配置裏去改)。

export default {
    name: 'Input',
    props: {
      value: { // 必須爲value
        type: Number,
        default:()=>''
      }
    },
    data() {
      return {
        currValue: this.value
      }
    },
    watch: {
      value (val) {
        this.currValue = val;
      }
    },
    methods: {
      change (val) {
        this.$emit('input', this.currValue); // 必須爲 input
      }
    }
  }
複製代碼

.sync 在說組件通訊時,數據的通訊是單向的,當咱們給組件傳入一個數字型,字符串或者布爾類型值時,子組件若是修改了這個變量,父組件是不會更新的,咱們只能經過 $emit() 方式進行提交,雖然這樣有利於維護數據,可是有時候咱們確實須要一些雙向綁定效果,最經典的就是對話框組件的顯示與否了:

<el-dialog :visible.sync="showDialog"></el-dialog>
複製代碼

Vue.js 2.3 版本以後能夠在變量後加 .sync,這樣就能夠經過布爾型變量 showDialog 來控制對話框顯示與否了,這個過程並且會變成雙向的,也不用寫一個事件來手動控制了。雖然外部不須要寫一個事件,可是 el-dialog 組件內必定要手動觸發一下 update:visible 事件:

close(){ this.$emit('update:visible', false) }

9.其餘

$attrs 通常來講,咱們定義一個組件的屬性是這樣的:

<test :a="msg1" :b="msg2"></test>
複製代碼

在組件內部 props 必須寫上 a 和 b:

props:{
 a:{},
 b:{}
}
複製代碼

假如 test 組件內又嵌套了一個組件 test2,它的屬性繼承了 props 中的 a 和 b,test.vue:

<template>
  <div>
    <test2 :a="a" :b="b"></test2>
  </div>
</template>
<script>
export default {
  props:{
    a:{
      type:String,
      default:()=>''
    },
    b:{
      type:String,
      default:()=>''
    }
  }
}
</script>
複製代碼

這樣層層傳遞其實很麻煩,不少屬性重複寫了,其實每個組件內默認狀況下都有一個對象 $attrs,這個對象存儲了全部定義在這個組件的屬性,除了 props 裏已經定義好了的,也除了 class 和 style。

<test :a="msg1" :b="msg2" c="msg3" class="test"></test>
複製代碼

假如 a 和 b 在組件內部 props 裏聲明瞭,則組件內部 $attrs 爲:

{
  c:'msg3'
}
複製代碼

並且可使用 v-bind 將 $attrs 傳遞給子孫組件。test2.vue:

<template>
  <div>
    <test3  v-bind="$attrs"></test3>
  </div>
</template>
複製代碼

這樣省略了好多步驟和代碼。 $listeners $attrs 存儲的是組件屬性集合,而 $listeners 則是存儲了組件上的事件:

<test @test1="showMsg1()" @test2="showMsg2()"></test>
複製代碼

在組件內打印一下 $listeners:

{
  test1:"showMsg1",
  test2:"showMsg2"
}

複製代碼

咱們可使用 v-on 將 $listeners 傳遞給 test 組件的子組件,test2.vue:

<template>
  <div>
    <test3  v-on="$listeners" @test3="showMsg2()"></test3>
  </div>
</template>
複製代碼

在 test3 內,因爲自身還定義了一個事件 test3,因此 test3 內的 $listeners 一共有了三個事件了:

{
  test1:"showMsg1",
  test2:"showMsg2",
  test3:"showMsg3"
}
複製代碼

若是在這裏去觸發 test1,則最外面的 test.vue 組件則會響應, test3.vue:

this.$emit("test1",val)
test.vue:
<test @test1="showMsg1()"></test>
showMsg1(val){
    console.log(val)
}
複製代碼

這說明什麼?這說明咱們能夠跨級 $emit 了,這尤爲適合在一些沒有依賴 Vuex 的公共組件內使用。

##小結: 但願對新手們讀完有所收穫,後期會對此文章不間斷維護。

相關文章
相關標籤/搜索