组件定义
- // 全局定义
- vue.component('my-lists', {// options})
定义后, 可以在 template 模版中使用<my-list></mylist>, 此时会将组件中定义的内容显示出来
- <template>
- <div>
- <my-list></mylist>
- </div>
- </template>
通过例子来看一下
- <body>
- <div id='app'>
- <my-list></my-list>
- </div>
- </body>
- <script>
- Vue.component('my-list', {
- template: `
- <ul>
- <li > 上海</li>
- <li > 北京</li>
- <li > 南京</li>
- </ul>
- `
- })
- new Vue({
- el: '#app'
- })
- </script>
页面会显示出我们定义的 my-list 的组件内容,`` 这个符号是 ES6 中的模版字符串, 想详细了解的话, 可以看 MDN 上关于模版字符串的说明
如果不需要全局定义的话, 那可以在 Vue 实例中局部定义
- var myList = {
- template: `
- <ul>
- <li > 上海</li>
- <li > 北京</li>
- <li > 南京</li>
- </ul>
- `
- }
- new Vue({
- el: '#app', // 挂载
- components: {
- 'my-list': myList
- }
- })
组件内的 data
上面是简单的组件, 如果我们想自己通过变量, 来控制列表的展示, 这个时候, 我们组件内部定义 data 我们来重新写 my-list 组件:
- var citys = ['上海', '北京', '广州']
- Vue.component('my-list', {
- template: `
- <ul>
- <li v-for='(item, index) in citys' :key='index'>{{item}}</li>
- </ul>
- `,
- data () {
- return {
- citys: citys
- }
- }
- })
- new Vue({
- el: '#app'
- })
有一点要注意, data 必须是一个函数, 将值通过函数返回如果是一个变量:
- var citys = ['上海', '北京', '广州'] Vue.component('my-list', {
- template: ` <ul> <li v -
- for = '(item, index) in citys': key = 'index'> {
- {
- item
- }
- } </li>
- </ul> `,
- data: {
- citys: citys
- }
- })
会导致一个问题, 多个相同组件同时使用时,它们的 data 是共享的, 会出现相互影响的情况类似于浅拷贝的这种情况
- var a = {
- name: 'a'
- }
- var b = a
- var c = a console.log(b.name, c.name) // a a
- console.log(b.name, c.name) // a a
- b.name = 'b'console.log(b.name) // b
- console.log(c.name) // b
- // b 是 copy a 的引用, 所以 b 改变了的同时会影响 a
- // 组件内的 data 同理, 所以将 data 的值以函数返回, 以创建不同的对象, 而不是共享一个
- var a = function() {
- return {
- name: 'a'
- }
- }
- var b = a() // b 是新对象
- var c = a() // c 也是新对象
- b.name = 1 console.log(c.name) // a
父子组件的通信
父组件通过传递 Props 给子组件
子组件获取到父组件传递的值, 执行一些列操作后, 通过 emit 触发事件, 告诉父组件发生了什么
父组件可以见通过 v-on/@监听这个事件
以上述的my-list 为例子, 实现当点击城市名称时,会在下方显示点击的城市:
- <body>
- <div id='app'>
- <my-list
- :citys='citys'
- @select='getCity'></my-list>
- <p>{{selCity}}</p>
- </div>
- </body>
- <script>
- Vue.component('my-list', {
- template: `
- <ul>
- <li
- v-for='(item, index) in citys'
- :key='index'
- @click='select(item)'>{{item}}</li>
- </ul>
- `,
- props: ['citys'],
- methods: {
- select (city) {
- this.$emit('select', city)
- }
- }
- })
- new Vue({
- el: '#app',
- data () {
- return {
- citys: ['上海', '北京', '广州'],
- selCity: ''
- }
- },
- methods: {
- getCity (city) {
- this.selCity = city
- }
- }
- })
- </script>
:citys 动态绑定 props 的用户,: 是 v-bind 的缩写; 如果传递的是一个静态的值, 则可以直接写成
citys="['上海','北京']"
@select 父组件监听子组件触发的事件
this.$emit('select', city)
; @是 v-on 的缩写; 如果子组件触发了 select, 会响应父组件的'getCity'函数
我们需要注意的是, 父子组件通信是单向的, 即父组件传递给子组件的props 发生变化时, 子组件也会接收到这种变化, 但是如果子组件改变 props 的值时, 不会反馈给父组件, 防止修改了父组件的状态, 导致整个的数据流难以理解, 如果修改了, Vue 也会发出警告 props 对于子组件来说应该是只读的有两种情况下, 我们会想改变 props:
props 传递进来后, 我们需要对 props 进行处理,然后输出到页面上, 这种情况下, 可以通过 computed 来计算处理后的变量
- computed: {
- newCitys () {
- this.citys.filter((ele) => {
- return ele !== '上海'
- })
- }
- }
作为一个初始值, 后续需要对其进行修改, 计算等; 这种情况下, 可以将 props赋值给自身的属性
- data () {
- return {
- initCitys: this.citys
- }
- }
如果我们想修改 props 的值, 并将变化反馈给父组件, 除了 $emit 触发事件 + 父组件监听事件这种方式外, 还有一种较为简单的方式, 通过 sync 操作符
- // 父组件
- <child-component :name.sync='name'></child>
- // 子组件想要能够修改 name,可以这么做
- this.$emit('update:name', 'newVal')
上述我们是直接传递props, 我们可以在子组件中通过定义 props 的格式来对 props 进行简单的验证, 以下一段验证代码来自官网, 允许偷个懒 = _=||
- Vue.component('example', {
- props: {
- // 基础类型检测 (`null` 指允许任何类型)
- propA: Number,
- // 可能是多种类型
- propB: [String, Number],
- // 必传且是字符串
- propC: {
- type: String,
- required: true
- },
- // 数值且有默认值
- propD: {
- type: Number,
- default: 100
- },
- // 数组 / 对象的默认值应当由一个工厂函数返回
- propE: {
- type: Object,
- default: function () {
- return { message: 'hello' }
- }
- },
- // 自定义验证函数
- propF: {
- validator: function (value) {
- return value> 10
- }
- }
- }
- })
多个同级组件间的通信
在复杂项目中, 可能会出现多个同级的组件需要通信, 而仅仅是简单的父子组件通信, 这个时候怎么去做呢?
通过新建一个 Vue 实例, 作为事件总线
var emitEvent = new Vue()
- // 在不同的组件中, 通过触发事件,监听事件来实现通信
- emitEvent.$emit('event-name', val)
- emitEvent.$on('event-name', funciton (val) {
- })
统一状态管理, 可以在项目中使用 Vuex
Vuex 官网
Vuex 是一个专门为 vue.js 开发状态管理软件 Vuex 中有几个核心的概念
State 存储状态, 类似于 data 属性
Getter 从 state 中派生出一些属性, 类似于 computed
Mutation 更改 state 的属性, state 属性是无法直接通过赋值进行更改的, 需要通过 Mutation 定义的函数来进行赋值, 提交的是 state
Actions Mutation 必须是同步的函数, 所以在 Actions 中处理异步的操作, 并且提交的是Mutation
Module 当项目很大的时候,需要根据不同的功能分割成不同的模块,每次需要的时候, 只需要引用需要的模块即可
比如一个项目中使用 Vuex, 首先安装 Vuex
npm install vuex --save
, 通过脚手架vue-cli, 一般默认自带 Vuex
Vuex.store 的目录结构可以是这样的
- store
- - index.js // 统一模块的 index
- - modules
- - a.js // 模块 a
- - b.js // 模块 b
- store/index.js
- import Vue from 'vue'
- import Vuex from 'vuex'
- import a from './modules/a'
- import b from './modules/b'
- Vue.use(Vuex)
- export default new Vuex.Store({
- modules: {
- a,
- b
- }
- })
- store/modules/a.js
- export default {
- namespaced: true, // 必要的
- state: {
- data: null
- },
- mutations: {
- setData(state, data) {
- state.data = data
- }
- },
- actions: {
- getData (context, cb) {
- // 假设执行的从服务端获取的数据
- http.post(url, {name: 'b'}, function (res) {
- // 提交 mutation 操作
- context.commit('setData', res)
- // 如果有回调函数的话
- typeof cb === 'funciton' && cb(res)
- })
- }
- }
- }
store/modules.b.js 类似于 a, 在此就不举例子了
在组件中如何是使用呢?
首先导入 Vuex 提供的方法
- // components/a.vue
- // 首先, 确定需要哪些功能
- // 如果只需要 state 中的数据
- import { mapState } from 'vuex'
- // 如果只需要 state 中的数据 和 mutations 操作
- import { mapState, mapMutations } from 'vuex'
- // 如果还对 Actions 有需求
- import { mapState, mapMutations, mapActions } from 'vuex'
- // 需要什么导入什么即可
- // 小 tips: 可以先写 from 'vuex', 再写 {} 中的, 编辑器会自动不全
然后导入我们在 Store 中定义的属性和方法
- # 使用 a 模块, 并且把 a 模块中的data 赋值给 aData
- computed: {
- ...mapState('a', {
- aData: state => state.data
- })
- ...mapGetters('a', {
- xxx: state => state.xxx
- })
- }
- # 导入方法
- methods: {
- ...mapMutations('a', [
- 'setData'
- ])
- ...mapActions('a', [
- 'getData'
- ])
- }
使用属性和方法
- // 属性和方法通过 this 调用
- this.aData
- // 重新赋值
- this.setData('aaa')
- // 传入回调函数
- this.getData(function (res) {
- console.log(res)
- })
上面的代码中我们使用... Javascript 的扩展操作符和
aData: state => state.data
的箭头函数, 如果对这两项有不了解的, 可以查阅链接
扩展操作符
箭头函数
来源: https://juejin.im/post/5aa2b4adf265da23953097c2