Vue.js: некоторые приемы, о которых вы не знали

Vue.js

Интервьюер: В чем разница между MVVM и MVC?

Подумайте минуту.

vue.js

Для конкретного объяснения приведенных выше вопросов интервью, пожалуйста, перейдите кздесь, эта статья не является исчерпывающей. В начале текста часть советов, перечисленных ниже, может быть использована вами, а часть может не использоваться вами. Что бы это ни было, я надеюсь, что вы что-то вынесете из него после прочтения. Написание и знания ограничены.Если вы ошибаетесь, пожалуйста, оставьте сообщение, чтобы исправить это!

Установить несколько типов для свойства реквизита

Этот прием больше используется при разработке компонентов, для большей отказоустойчивости, да и код более удобен для пользователя:

export default {
  props: {
    width: {
      type: [String, Number],
      default: '100px'
    }
    // 或者这样
    // width: [String, Number]
  }
}

например<my-button>разоблачилwidthсвойства, мы можем либо передать100px, вы также можете пройти100:

<!-- my-button.vue -->
<template>
  <button :style="computedWidth">{{ computedWidth }}</button>
</template>

<script>
  export default {
    props: {
      width: [String, Number]
    },
    computed: {
      computedWidth () {
        let o = {}
        if (typeof this.width === 'string') o.width = this.width
        if (typeof this.width === 'number') o.width = this.width + 'px'
        return o
      }
    }
  }
</script>

использовать:

<!-- 在其他组件中使用 -->
<template>
  <my-button width="100px"></my-button>
  <!-- or -->
  <my-button :width="100"></my-button>
</template>

Элемент загрузить, как подсказать, а потом удалить?

Когда коллега загружает и удаляет, он добавляет пользователю запрос «Хотите удалить это?»取消операции, он все равно будет удален из списка загрузки. Глядя на документацию, говорится, что если в ``

Отключить автозаполнение браузера

Иногда мы вводим пароль учетной записи для входа в фоновую систему, и в браузере появляется всплывающее окно, нужно ли сохранять данные для входа. Обычно мы нажимаем «Сохранить», потому что в следующий раз, когда мы снова войдем в систему, он автоматически заполнится, чтобы улучшить наше взаимодействие с пользователем, и это хорошо.

Но иногда, когда мы разрабатываем определенный блок (например, добавление пользователя), нажмите кнопку добавления пользователя, чтобы отобразить всплывающее окно, к сожалению, браузер заполняет поле ввода учетной записи и пароля для нас. Но это не то, чего мы хотим. Итак, мое эффективное решение выглядит следующим образом:

  • настраивать<el-input/>в режиме только для чтения
  • существуетfocusУдалить режим только для чтения из событий

код показывает, как показано ниже:

<el-input
  type="text"
  v-model="addData.loginName"
  readonly
  @focus="handleFocusEvent"
/>
...
methods: {
  handleFocusEvent(event) {
    event.target && event.target.removeAttribute('readonly')
  }
}

Однако ElementUI поставляется сauto-complete="off"Кажется, это не работает.

Не допустить<el-form>Поведение фиксации по умолчанию

Иногда мы используем компонент Ele.me<el-form>Ввод ярлыка ввода в текстовое поле по умолчанию вызовет обновление страницы. Мы можем исправить его поведение по умолчанию, добавив следующий код:

<el-form @submit.native.prevent>
  ...
</el-form>

использовать<el-scrollbar>компоненты

ElementОфициальный явно не предоставляет этот компонент, но егоисходный кодсуществует в. Я написал Демо о том, как его использовать, вы можете безжалостнокликните сюдаПосмотрите пример, который не будет повторяться в этой статье.

По объединению бизнесаel-tableстроки и столбцы в

Недавно, когда я работал над проектом, я столкнулся с требованием:Если для одного и того же идентификатора учетной записи назначено несколько ролей, информация о роли отображается рядом.. Так что я подумалel-tableПредоставленный метод слияния:span-method. Но у него есть требования к формату фоновых данных:

  • Если данные, возвращаемые в фоновом режиме, представляют собой вложенный массив в массив, вам нужно вывернуть массив наизнанку по порядку.
// 假如后台返回的数据是下面这样的
{
  data: [
    { id: 1, appkey: 1, name: 'a', list: [{ id: 11, appkey: 1, name: 'a-1'}, {id: 12, appkey: 1, name: 'a-2'}] }
    { id: 2, appkey: 2, name: 'b', list: [{ id: 21, appkey: 2, name: 'b-1'}, {id: 22, appkey: 2, name: 'b-2'}] }
  ]
}

// 整理过后的格式应该是这样的
{
  data: [
    { id: 1, appkey: 1, name: 'a' },
    { id: 11, appkey: 1, name: 'a-1' },
    { id: 12, appkey: 1, name: 'a-2' },
    { id: 2, appkey: 2, name: 'b' },
    { id: 21, appkey: 2, name: 'b-1' },
    { id: 22, appkey: 2, name: 'b-2' }
  ]
}

Ниже приведен конкретный поток обработки:

<template>
  <el-table 
    :data="formattedList" 
    :span-method="handleColspanMethod"
  >
  ...
  </el-table>
</template>
<script>
import Api from '@/api/assign'
export default {
  data() {
    return {
      list: [], // 后台返回的数据
      formattedList:[], // 格式化后的数据
      spanArr: [], // 保存要合并的行列数据
    }
  },
  created() {
    this.getList()
  },
  methods: {
    getList() {
      Api.fetchList().then(response => {
        this.list = response.data.data
        // 格式化数据
        this.formattedList = this.formatArray(this.list)
        // 获取合并位置
        this.getSpanArr()
      })
    },
    /**
     * 格式化数组
     * {Array} sources 源数组
     * {Array} arrayed 格式化后的数组
     * return 返回格式化后的数组
     */
    formatArray: function f(sources, arrayed) {
      if (!sources) return []
      
      const arr = arrayed || []
      const len = sources.length

      for (let i = 0; i < len; i++) {
        const childs = sources[i].list || []
        arr.push(sources[i])
        if (childs.length > 0) {
          f(sources[i].list, arr)
        }
      }
      return arr
    },
    /**
     * 获取合并位置信息
     */
    getSpanArr() {
      // 重置 spanArr,因为翻页的时候数据就变了
      // 之前的数据如果不清空,其他页也会受影响
      this.spanArr = []
      const data = this.formattedList
      if (!data || data.length <= 0) return
      // 遍历
      for (let i = 0; i < data.length; i++) {
        if (i === 0) {
          this.spanArr.push(1)
          // 其实就是行索引
          this.pos = 0
        } else {
          // 如果当前对象和上一个对象的 appkey 相等
          // 说明需要合并
          if (data[i].appkey === data[i - 1].appkey) {
            this.spanArr[this.pos] += 1
            this.spanArr.push(0)
          } else {
            this.spanArr.push(1)
            this.pos = i
          }
        }
      }
    },
    /**
     * 处理跨行列合并
     */
    handleColspanMethod({ row, column, rowIndex, columnIndex}) {
      if (columnIndex < 2) {
        const _spa = this.spanArr[rowIndex]
        const _row = _spa ? _spa : 0
        const _col = _row > 0 ? 1 : 0
        return {
          rowspan: _row,
          colspan: _col
        }
      }
    }
  }
}
</script>

Извлечение в стиле одного файла

еслиa.vueиb.vueИспользуются следующие стили:

.btn {
  color: silver
}
// 省略很长的样式
...

Рассмотрите возможность извлечения стилей вstyleName.scss/css, Например:

./components/common.scss

.btn {
  color: silver
}
// 省略很长的样式

Затем внесите его в два файла, а именно:

<template>...</template>
<script>...</script>
<style lang="scss" src="./components/common.scss" scoped/>

Это экономит много кода? Иди и попробуй!

решатьvue-template-adminПроблема пути 404 после того, как фоновое изображение в одном файле создано и упаковано

  • оказатьсяbuild/utils.js
  • затем найтиgenerateLoadersметод
  • существуетif(options.extract){...}изменены, а именно:
if (options.extract) {
  // 解决其打包背景图片路径问题
  loaders.push({
    loader: MiniCssExtractPlugin.loader,
    options: {
      publicPath: '../../'
    }
  })
} else {
  ...
}

Помните, никогда не пишите это в вашем css:

background: url("/new/static/images/assets/loginbackground.png");

так какnewПапки настраиваются для вас одноклассниками в фоновом режиме и могут быть изменены в любое время.Одно место — это хорошо, но менять несколько мест мучительно.

dataинициализация

так какpropsчемdataСначала выполняется инициализация, поэтому мы можем воспользоваться этим, чтобы датьdataИнициализируйте некоторые данные, посмотрите на код:

export default {
  data () {
    return {
      buttonSize: this.size
    }
  },
 props: {
   size: String
 }
}

В дополнение к вышесказанному, подкомпонентdataФункции также могут иметь параметры, и этот параметр является текущим объектом экземпляра. Таким образом, мы можем использовать это, чтобы сделать некоторые собственные суждения. Например, перепишите приведенный выше код:

export default {
  data (vm) {
    return {
      buttonSize: vm.size
    }
  },
 props: {
   size: String
 }
}

template

мы делаемv-ifПри судействе вы можете поставить условие суждения вtemplateкомпонент,наконец-тоВизуализированный результат не будет содержать<template>элемент.

<template>
  <div class="box">
    <template v-if="isVal">
      <h2>...</h2>
    </template>
    <template v-else>
      <h2>...</h2>
    </template>
  </div>
</template>

v-forТо же самое относится.

Lifecycle hook

Хук жизненного цикла может быть типа массива, и функции в массиве будут выполняться последовательно.

export default {
 ...
 created: [
   function one () {
     console.log(1)
   },
   function two () {
     console.log(2)
   }
 ]
 ...
}

Это бесполезно, просто знай это. На самом деле хуки жизненного цикла также могут воздействовать на элементы DOM, благодаря чему мы можем использовать методы родительского компонента для инициализации хуков жизненного цикла дочерних компонентов:

<!-- Child.vue -->
<template>
  <h3>I'm child!</h3>
</template>

<!-- Parent.vue -->
<template>
 <child @hook:created="handleChildCreated"></child>
</template>

<script>
   import Child from './child.vue'
   export default {
     components: [ Child ],
     methods: {
       handleChildCreated () {
         console.log('handle child created...')
       }
     }
   }
</script>

Остальные хуки такие же и здесь повторяться не будут.

v-for

С использованиемv-forПри обходе массива мы обычноНеправильноСделайте это, например, так:

Используйте v-for и v-if для одного и того же элемента:

<template>
  <ul class="items">
    <!-- 只有激活的用户才可以显示 -->
    <li 
      v-for="(user, index) in users" 
      v-if="user.isActive" 
      :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

так какv-forиv-ifИспользование его на одном и том же элементе будет иметь некоторые последствия для производительности., официальное предложение состоит в том, чтобы пройти после фильтрации по вычисляемому свойству. Поэтому не рекомендуется использовать его вместе в обычном развитии, достаточно знать, что такое есть, чтобы не узнать его на собеседовании. Почему не рекомендуется использовать вместе, см.Избегайте использования -v-if- и -v-for- вместе.

смешивание

Если многие компоненты имеют что-то вродеprops,data,methodsд., можно вынуть отдельно и положить вmixinsв миксере. Например, он используется в списке управления пользователями.

Смеситель страниц:

// paging-mixin.vue
export default {
  props: {
    pageSize: 1,
    pageLength: 10,
    currentPage: 1
    total: 20
  },
  methods: {
    /**
     * 上一页
     */
    prevPage (page) {
      ...
    },
    /**
     * 下一页
     */
    nextPage (page) {
      ...
    }
    /**
     * 跳转到当前页
     */
    currentPage (page) {
      ...
    }
  }
}

Users.vue:

<template>
  <div class="user-model">
    <my-table :data="users"></my-table>
    <my-paging
      :page-length="pageLength"
      :page-size="pageSize"
      :current-page="currentPage"
      :total="total">
    </my-paging>
  </div>
</template>

<script>
  import PagingMixin from '../mixins/paging-mixin.vue'
  export default {
    mixins: [PagingMixin],
    data () {
      return {
        users: [],
        pageLength: 10,
        pageSize: 1,
        currentPage: 1,
        total: 20
      }
    }
  }
</script>

Не нужно писать на каждой страницеpropsиmethods.

функция рендеринга

Вот простой код шаблона шаблона:

<template>
  <div class="box">
    <h2>title</h2>
    this is content
  </div>
</template>

мы используемфункция рендерингаЧтобы переписать приведенный выше код:

export default {
  render (h) {
    let _c = h
    return _c('div', 
      { class: 'box'}, 
      [_c('h2', {}, 'title'), 'this is content'])
  }
}

На самом деле Vue скомпилирует шаблон (template) в функцию рендеринга (render), вы можете передатьонлайн-инструментыПросмотр результатов компиляции в режиме реального времени. Приведенный выше шаблон шаблона будет скомпилирован в следующую функцию рендеринга:

let render = function () {
  return _c('div',
    {staticClass:"box"},
    [_c('h2', [_v("title")]), _v("this is content")])
}

Это похоже? Как сказал чиновник,Функции рендеринга ближе к компилятору, чем шаблоны. Если вы используете блок-схему, чтобы объяснить это, это, вероятно, выглядит так:

template
    ↓
预编译工具(vue-loader + vue-template-compile)
    ↓
  render
    ↓
resolve vnode

Подробнее см.Диаграмма жизненного цикла Vue.

Использование функции рендеринга:

  • Разработать библиотеку компонентов, исходный код элемента использует рендеринг
  • Инкапсулируйте некоторые компоненты более высокого порядка. Компонент, вложенный в компонент, является компонентом более высокого порядка при условии, что выполняются три элемента компонента:props,event,slot
  • Используется для решения некоторых сложных логических суждений. Если у нас есть много внутри компонентаv-ifЕсли вы рассудите, использовать шаблон нецелесообразно, в настоящее время вы можете использовать функцию рендеринга, чтобы легко с этим справиться.

errorCaptured

Вызывается при обнаружении ошибки от компонента-потомка. Иногда это бывает полезно, когда мы хотим собирать журналы ошибок, но не хотим показывать ошибки в консоли браузера. Вот пример:

Child.vue

<template>
  <!-- 省略一些无关代码 -->
</template>
<script>
  export default {
    mounted () {
      // 故意把 console 写错
      consol.log('这里会报错!')
    }
  }
</script>

Parent.vue

<template>
  <child></child>
</template>
<script>
  import Child from './Child.vue'
  export default {
    components: [ Child ],
    /**
     * 收到三个参数:
     * 错误对象、发生错误的组件实例
     * 以及一个包含错误来源信息的字符串。
     * 此钩子可以返回 false 以阻止该错误继续向上传播。
     */
    errorCaptured (err, vm, info) {
      console.log(err)
      // -> ReferenceError: consle is not defined ...
      console.log(vm)
      // -> {_uid: 1, _isVue: true, $options: {…}, _renderProxy: o, _self: o,…}
      console.log(info)
      // -> `mounted hook`
      // 告诉我们这个错误是在 vm 组件中的 mounted 钩子中发生的
      
      // 阻止该错误继续向上传播
      return false
    }
  }
</script>

Для получения дополнительной информации об errorCaptured, пожалуйста,Перейти на официальный сайт->.

v-once

пройти черезv-onceСоздавайте статические компоненты с низкими накладными расходами. Рендеринг обычных HTML-элементов в Vue происходит очень быстро, но иногда у вас может быть компонент, содержащий много статического содержимого. В этом случае вы можете добавить корневой элементv-onceфункция, чтобы гарантировать, что они вычисляются только один раз, а затем кэшируются, например:

<template>
  <div class="box" v-once>
    <h2> 用户协议 </h2>
    ... a lot of static content ...
  </div>
</template>

Визуализируйте элементы и компоненты только один раз. При последующем повторном рендеринге элемент/компонент и все его дочерние элементы будут рассматриваться как статическое содержимое и будут пропущены. Это можно использовать для оптимизации производительности обновления. оv-onceДля более подробного ознакомления, пожалуйстаПерейти на официальный сайт->.

slot-scope

Слот области.vue@2.5.0версия, ранее называвшаясяscope, более поздние версии используютslot-scopeзамени это. за исключением того, что область может быть использована только<template>элементы, другие иslot-scopeвсе так же.

Студенты, которые использовали компоненты Element, знают, что когда мы используем<el-table>Когда вы видите следующий код:

Element@1.4.xверсия:

<el-table-column label="操作">
  <template scope="scope">
  <el-button
    size="small"
    @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
  <el-button
    size="small"
    type="danger"
    @click="handleDelete(scope.$index, scope.row)">删除</el-button>
  </template>
</el-table-column>

Но в версиях после 2.0 его заменили наslot-scope.

Element@2.0.11:

<el-table-column label="操作">
  <template slot-scope="scope">
    <el-button
      size="mini"
      @click="handleEdit(scope.$index, scope.row)">编辑</el-button>
    <el-button
      size="mini"
      type="danger"
      @click="handleDelete(scope.$index, scope.row)">删除</el-button>
  </template>
</el-table-column>

если быть честным,slot-scopeЭто равносильно обратному вызову функции, я дам вам результат, вы можете распоряжаться им как хотите, все на ваше усмотрение:

function getUserById (url, data, callback) {
  $.ajax({
    url,
    data,
    success: function (result) {
      callback(result)
    }
  })
}

// 使用
getUserById('/users', { id: 1 }, function (response) {
  // 拿到数据并开始处理自己的页面逻辑
})

Теперь давайте сделаем простую симуляцию<el-table>Как использовать внутри компонентаslot-scopeДа, посмотрите на код:

смоделированный<el-table>Компоненты:

// 定义模板
let template = `
 <ul class="table">
  <li v-for="(item, index) in data" :key="index">
    <!-- 我希望数据由调用者自己处理 -->
    <!-- 'row' 相当于变量名,随便定义,比如 aaa,bbb 啥的 -->
    <slot :row="item">
      <!-- 当使用者什么都没写的时候,默认值才会显示-->
      {{ item.name }}
    </slot>
  </li>
 </ul>
`
Vue.component('el-table', {
  template,
  props: {
    data: Array,
    default: []
  }
})

используйте там, где вам это нужно<el-table>Компоненты:

HTML:

<div id="app">
  <el-table :data="userData">
    <!-- 使用的时候可以用 template -->
    <!-- `scope` 也是个变量名,随便命名不是固定的,比如 foo, bar -->
    <template slot-scope="scope">
      <!-- 其中 `scope.row` 中的 row 就是我们上边定义的变量啦-->
      <!-- `scope.row`返回的是 `item` 对象 -->
      <template v-if="scope.row.isActived">
        <span class="red">{{ scope.row.name }}</span>
      </template>
      <template v-else>
        {{ scope.row.name }}
      </template>
    </template>
  </el-table>
</div>

JavaScript:

new Vue({
  el: '#app',
  data: {
    userData: [
      {id: 1, name: '张三', isActived: false},
      {id: 2, name: '李四', isActived: false},
      {id: 1, name: '王五', isActived: true},
      {id: 1, name: '赵六', isActived: false},
    ]
  }
})

CSS:

.red {
  color: red
}

ты можешь быть жесткимкликните сюдаОцените эффект выше! Наконец, мы используем функцию рендеринга для рефакторинга приведенного выше кода:

JavaScript:

// `<el-table>` 组件
Vue.component('el-table', {
  name: 'ElTable',
  render: function (h) {
    return h('div', { 
      class: 'el-table'
    }, this.$slots.default)
  },
  props: {
    data: Array
  }
})

// `<el-table-column>`
Vue.component('el-table-column', {
  name: 'ElTableColumn',
  render: function (h) {
    // 定义一个存放 li 元素的数组
    let lis = [], 
       // 获取父组件中的 data 数组
       data = this.$parent.data
    // 遍历数组,也就是上面的 `v-for`,生成 `<li>` 标签
    // `this.$scopedSlots.default` 获取的就是上面 slot-scope 作用于插槽的部分,
    // 并把 `{ row: item }` 传给上面的 `scope` 变量
    data.forEach((item, index) => {
      let liEl = h('li', {
        key: item.id
      }, [ this.$scopedSlots.default({ row: item }) ])
      // 把生成的 li 标签存到数组
      lis.push(liEl)
    })
    return h('ul', {
      class: 'el-table-column'
    }, lis)
  }
})

Используйте это на своей странице следующим образом:

HTMl:

<div id="app">
  <el-table :data="list">
    <el-table-column>
      <template slot-scope="scope">
        <span class="red" v-if="scope.row.actived">{{ scope.row.name }}</span>
        <span v-else>{{ scope.row.name }}</span>
      </template>
    </el-table-column>
  </el-table>
</div>

JavaScript:

new Vue({
  el: '#app',
  data: {
    list: [
      { id: 1, name: '张三', actived: false },
      { id: 1, name: '李四', actived: false },
      { id: 1, name: '王五', actived: true },
      { id: 1, name: '赵六', actived: false },
    ]
  }
})

ты можешь быть жесткимкликните сюдаОцените эффект выше!

Вопрос: мы можем<li>Почему мы должны выносить его на улицу для обработки? Потому что иногда мы не используем компоненты, разработанные нами, такие как выше<el-table>, так и надо сделать.

конец!

Сказав так много, я надеюсь, что одноклассники, которые это увидят, приобретут больше или меньше. Пожалуйста, оставьте сообщение, чтобы исправить неправильное место, большое спасибо. Как говорится,Если вы втроем, у вас должен быть мой учитель!Надеюсь, что больше друзей, увлеченных Vue, смогут собраться вместе, чтобы обменяться технологиями! Ниже приведена группа Q, которую я поддерживаю, добро пожаловать, чтобы отсканировать код, чтобы присоединиться к группе, давайте общаться и учиться вместе. Вы также можете добавить мой личный WeChat: G911214255, комментарии掘金Вот и все.Q1769617251