zoukankan      html  css  js  c++  java
  • Vue 入门笔记

    # vuejs tutorial

    ![vue-logo](img/logo.png)

    ---

    ## 搭建案例演示自动刷新环境

    创建一个 `package.josn` 文件:

    ```bash
    npm init -y
    ```

    安装 `browser-sync`:

    ```bash
    # npm install --save-dev browser-sync
    # 将 browser-sync 包保存到开发依赖中
    # 就可以执行 npm install 的时候加入 --production 选项不安装这个包
    npm i -D browser-sync
    ```

    在 package.json 文件中加入以下内容:

    ```json
    "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "browser-sync start --server --directory --files "code/*.html""
    }
    ```

    打开终端,启动开发预览服务:

    ```bash
    npm start
    ```

    ---

    ## vuejs 介绍

    ---

    ## 安装

    - Vue.js 不支持 IE8 及其以下版本,因为 Vue.js 使用了 IE8 不能模拟的 ECMAScript 5 特性
    - 每个版本的更新日志见:https://github.com/vuejs/vue/releases
    - 独立 js 文件
    + 开发版本(未压缩):http://vuejs.org/js/vue.js
    + 生产版本(压缩):http://vuejs.org/js/vue.min.js
    - CDN:https://unpkg.com/vue
    - NPM: `npm install vue`
    - Bower: `bower install vue`
    - 兼容 AMD 规范
    + 独立下载版本或通过 Bower 安装的版本已用 UMD 包装,因此它们可以直接用作 AMD 模块。

    ---

    ## Vue 实例

    - 每个 Vue.js 应用都是通过构造函数 Vue 创建的
    - 在实例化 Vue 时,需要传入一个选项对象,它可以包含数据、模板、挂载元素、方法、生命周期钩子等选项
    + data: 属性数据
    + computed: 计算属性
    + methods: 方法
    + el: 挂载点
    + directives: 自定义指令
    + filters: 自定义过滤器
    + ...
    + 全部选项可以在 API 文档中查看:https://cn.vuejs.org/v2/api/

    - 实例选项:data
    + https://cn.vuejs.org/v2/guide/instance.html#属性与方法
    + [选项/数据 - data](https://cn.vuejs.org/v2/api/#data)
    + [深入响应式原理](https://cn.vuejs.org/v2/guide/reactivity.html)
    + 作用:根据视图抽象数据模型
    + 注意:视图中使用的数据必须在 data 中初始化
    + 每个 VUe 实例都会代理其 data 对象里所有的属性
    * 也可以通过 vue 实例的 $data 属性访问 data 中的数据
    * 建议:如果要使用 vm 读取或修改 data 中的数据,建议加上 vm.$data 去访问
    + 只有被初始代理的属性是响应式的
    * 如果是在实例创建之后添加新的属性到实例上,它不会触发视图更新
    + Vue 不允许在已经创建的实例上动态添加新的根级响应式属性
    - Vue 不能检测到对象属性的动态添加或删除
    + 也就是说动态添加或删除的对象属性不是响应式的
    + 如果希望动态添加和删除对象的属性是响应式的则需要通过:
    * `Vue.set( object, key, value )`
    * 或 `vm.$set( object, key, value )`
    + 如果删除对象属性是响应式的:
    * `Vue.delete( object, key )`
    * 或 `vm.$delete( object, key )`

    - 实例选项:methods
    + https://cn.vuejs.org/v2/api/#methods
    + 作用:为视图交互提供行为函数
    + 可以在行为函数中通过 `this` 访问 data 中的数据成员
    + 注意:methods 中的行为函数不要写箭头函数
    * 因为这样会改变内部 this 的指向

    - 实例属性
    + https://cn.vuejs.org/v2/api/#实例属性
    + $data
    * Vue 实例观察的数据对象。Vue 实例代理了对其 data 对象属性的访问。
    + $el
    * Vue 实例使用的根 DOM 元素

    - 实例方法/数据
    + https://cn.vuejs.org/v2/api/#实例方法-数据
    + $watch
    + $set Vue.set 的别名
    + $delete Vue.delete 的别名

    ---

    ## 模板语法

    ### 插值

    #### 文本

    - 响应插值:
    + `<span>Message: {{ msg }}</span>`
    + 注意: Mustache 语法不能在 HTML 属性中使用,应使用 `v-bind` 指令
    - 一次性插值:
    + `<span v-once>This will never change: {{ msg }}</span>`
    + 注意:会影响该节点及内部节点所有的绑定

    #### 纯 HTML

    双大括号会将数据解释为纯文本,而非 HTML 。为了输出真正的 HTML ,你需要使用 v-html 指令:

    ```html
    <div v-html="rawHtml"></div>
    ```

    - 为什么不直接输出 HTML
    - 什么是 XSS 攻击:跨站脚本攻击
    + 后天或者后后天补课

    #### 属性

    **注意:Mustache 不能在 HTML 属性中使用,应使用 v-bind 指令:**

    ```html
    <div v-bind:id="dynamicId"></div>
    ```

    这对布尔值的属性也有效 —— 如果条件被求值为 false 的话该属性会被移除:

    ```html
    <button v-bind:disabled="someDynamicCondition">Button</button>
    ```

    #### 使用 JavaScript 表达式

    Vue.js 都提供了完全的 JavaScript 表达式支持:

    ```html
    {{ number + 1 }}

    {{ ok ? 'YES' : 'NO' }}

    {{ message.split('').reverse().join('') }}

    <div v-bind:id="'list-' + id"></div>
    ```

    这些表达式会在所属 Vue 实例的数据作用域下作为 JavaScript 被解析。
    有个限制就是,每个绑定都只能包含单个表达式,所以下面的例子都不会生效:

    ```html
    <!-- 这是语句,不是表达式 -->
    {{ var a = 1 }}

    <!-- 流控制也不会生效,请使用三元表达式 -->
    {{ if (ok) { return message } }}
    ```

    ### 指令

    ### Vue 内置指令

    - v-text
    + 和 {{}} 效果是一样
    + 但是 {{}} 会闪烁
    + 解决方法就是利用 v-text 绑定数据
    + 如果又想用 {{}}} 还需要避免闪烁
    + 使用 v-cloak 处理
    - v-html
    + 默认 {{}} 和 v-text 会把 html 格式字符串原样输出
    + 可以使用 v-html 将 html 格式字符串作为 HTML 渲染到节点中
    - v-show
    + 是否显示和隐藏
    - v-if
    + 是否渲染和移除
    - v-else
    + v-if 的 else 块
    - v-else-if
    + 是 v-if 的逻辑块
    + 同样的,也必须紧跟着 v-if
    - v-for
    + 循环遍历输出
    - v-on
    + DOM 元素的事件绑定
    + 例如:`v-on:click`、`v-on:blur`
    - v-bind
    + 动态绑定 HTML 属性
    + 例如:`v-bind:title`、`v-bind:class`
    - v-model
    + 和表单控件进行双向数据绑定
    - v-pre
    + 不处理指定节点及内部所有节点的 vue 规则
    + 例如可以用来显示原始的 Mustache 标签
    + 作用:跳过大量没有指令的节点可以加快编译速度
    - v-cloak
    + 可以处理表达式闪烁的问题
    - v-once
    + 一次性绑定,后续数据的更新不会响应

    指令(Directives)是带有 `v-` 前缀的特殊属性。
    指令属性的值预期是单一 JavaScript 表达式(除了 `v-for`,之后再讨论)。指令的职责就是当其表达式的值改变时相应地将某些行为应用到 DOM 上。

    ```html
    <p v-if="seen">Now you see me</p>
    ```

    这里, v-if 指令将根据表达式 seen 的值的真假来移除/插入 <p> 元素。

    #### 参数

    一些指令能接受一个“参数”,在指令后以冒号指明。
    例如, v-bind 指令被用来响应地更新 HTML 属性:

    ```html
    <a v-bind:href="url"></a>
    ```

    在这里 `href` 是参数,告知 `v-bind` 指令将该元素的 `href` 属性与表达式 `url` 的值绑定。

    另一个例子是 v-on 指令,它用于监听 DOM 事件:

    ```html
    <a v-on:click="doSomething">
    ```

    在这里参数是监听的事件名:`click`。

    #### 修饰符

    修饰符(Modifiers)是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。
    例如,.prevent 修饰符告诉 v-on 指令对于触发的事件调用 event.preventDefault():

    ```html
    <div>
    <input type="text" v-on:keyup.enter="xxx">
    </div>
    ```

    ```html
    <form v-on:submit.prevent="onSubmit"></form>
    <input type="text" v-on:keyup.enter="addTodo">
    ```

    ### 过滤器

    > 注意:Vue 2.x 中,过滤器只能在 mustache 绑定和 v-bind 表达式(从 2.1.0 开始支持)中使用,
    > 因为过滤器设计目的就是用于文本转换。为了在其他指令中实现更复杂的数据变换,你应该使用 **计算属性**。

    - Vue.js 允许你自定义过滤器,可被用作一些常见的文本格式化
    - 过滤器可以用在两个地方:`mustache 插值` 和 `v-bind 表达式`

    全局过滤器:

    ```js
    Vue.filter('capitalize', function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
    })
    ```

    局部过滤器:

    ```js
    new Vue({
    // ...
    filters: {
    capitalize: function (value) {
    if (!value) return ''
    value = value.toString()
    return value.charAt(0).toUpperCase() + value.slice(1)
    }
    }
    })
    ```

    过滤器使用格式:

    ```html
    <!-- in mustaches -->
    {{ message | capitalize }}

    <!-- in v-bind -->
    <div v-bind:id="rawId | formatId"></div>
    ```

    过滤器可以串联:

    ```html
    {{ message | filterA | filterB }}
    ```

    过滤器是 JavaScript 函数,因此可以接受参数:

    ```html
    {{ message | filterA('arg1', arg2) }}
    ```

    这里,字符串 'arg1' 将传给过滤器作为第二个参数,arg2 表达式的值将被求值然后传给过滤器作为第三个参数。

    ### 缩写

    #### v-bind 缩写

    ```html
    <!-- 完整语法 -->
    <a v-bind:href="url"></a>
    <!-- 缩写 -->
    <a :href="url"></a>
    ```

    #### v-on 缩写

    ```html
    <!-- 完整语法 -->
    <a v-on:click="doSomething"></a>
    <!-- 缩写 -->
    <a @click="doSomething"></a>
    ```

    ---

    ## 计算属性

    模板内的表达式是非常便利的,但是它们实际上只用于简单的运算。
    在模板中放入太多的逻辑会让模板过重且难以维护。例如:

    ```html
    <div id="example">
    {{ message.split('').reverse().join('') }}
    </div>
    ```

    在这种情况下,模板不再简单和清晰。
    这就是对于任何复杂逻辑,你都应当使用计算属性的原因。

    #### 基础例子:反转字符串

    ```js
    var vm = new Vue({
    el: '#example',
    data: {
    message: 'Hello'
    },
    computed: {
    // a computed getter
    reversedMessage: function () {
    // `this` points to the vm instance
    return this.message.split('').reverse().join('')
    }
    }
    })
    ```

    你可以像绑定普通属性一样在模板中绑定计算属性。
    Vue 知道 `vm.reversedMessage` 依赖于 `vm.message` ,
    因此当 `vm.message` 发生改变时,所有依赖于 `vm.reversedMessage` 的绑定也会被重新计算进行更新。

    ---

    ## Class 与 Style 绑定

    在 `v-bind` 用于 `class` 和 `style` 时, Vue.js 专门增强了它。
    表达式的结果类型除了 **字符串** 之外,还可以是 **对象** 或 **数组** 。

    ### 绑定 HTML Class

    #### 对象语法

    ```html
    <div v-bind:class="{ active: isActive }"></div>

    <!-- v-bind:class 指令可以与普通的 class 属性共存 -->
    <div class="static"
    v-bind:class="{ active: isActive, 'text-danger': hasError }">
    </div>
    ```

    也可以直接绑定数据里的一个对象:

    ```html
    <div v-bind:class="classObject"></div>
    <script>
    new Vue({
    data: {
    classObject: {
    active: true,
    'text-danger': false
    }
    }
    })
    </script>
    ```

    #### 数组语法

    ```html
    <!-- 可以把一个数组传给 v-bind:class ,以应用一个 class 列表 -->
    <div v-bind:class="[activeClass, errorClass]">

    data: {
    activeClass: 'active',
    errorClass: 'text-danger'
    }

    <!-- 根据条件切换列表中的 class ,可以用三元表达式: -->
    <div v-bind:class="[isActive ? activeClass : '', errorClass]">

    <!-- 可以在数组语法中使用对象语法: -->
    <div v-bind:class="[{ active: isActive }, errorClass]">
    ```

    ### 绑定内联样式

    ```html
    <!-- CSS 属性名可以用驼峰式(camelCase)或名短横分隔命(kebab-case) -->
    <div v-bind:style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div>

    data: {
    activeColor: 'red',
    fontSize: 30
    }

    <!-- 直接绑定到一个样式对象 -->
    <div v-bind:style="styleObject"></div>
    data: {
    styleObject: {
    color: 'red',
    fontSize: '13px'
    }
    }

    <!-- v-bind:style 的数组语法可以将多个样式对象应用到一个元素上 -->
    <div v-bind:style="[baseStyles, overridingStyles]">
    ```

    ---

    ## 条件渲染

    ### v-if-else-elseif

    ```html
    <!-- 基本用法 -->
    <h1 v-if="ok">Yes</h1>

    <!--
    通过 template 包装多个元素,渲染结果不包含 template
    v-else 元素必须紧跟在 v-if 或者 v-else-if 元素的后面——否则它将不会被识别。
    -->
    <template v-if="ok">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
    </template>

    <!-- 使用 v-else 指令来表示 v-if 的“else 块” -->
    <div v-if="Math.random() > 0.5">
    Now you see me
    </div>
    <div v-else>
    Now you don't
    </div>

    <!--
    v-else-if,顾名思义,充当 v-if 的“else-if 块”。可以链式地使用多次:
    v-else,,v-else-if 必须紧跟在 v-if 或者 v-else-if 元素之后
    -->
    <div v-if="type === 'A'">
    A
    </div>
    <div v-else-if="type === 'B'">
    B
    </div>
    <div v-else-if="type === 'C'">
    C
    </div>
    <div v-else>
    Not A/B/C
    </div>
    ```

    ---

    ## 列表渲染

    ---

    ## 事件处理器

    ---

    ## 表单控件绑定

    ---

    ## 组件

    组件是 Vue.js 最强大的功能,组件可以扩展自定义 HTML 元素,封装可重用代码。

    ### 组件的命名

    - 如果一个单词就只写一个单词即可
    - 如果是多个单词组成的名字
    + 建议使用短横杠的方式
    - 如果使用的是驼峰命名
    + 则在 DOM 模板中必须将 驼峰命名的组件名改为短横杠的方式
    + 在 字符串模板中,无论是驼峰还是短横杠都行

    ### 组件基础

    - 注册全局组件
    - 注册局部组件
    - 组件的模板
    - 组件的 data

    #### 注册全局组件:`Vue.component(tagName, options)`

    注册:

    ```js
    Vue.component('my-component', {
    template: '<div>A custom component!</div>'
    })
    ```

    使用:

    ```html
    <div id="example">
    <my-component></my-component>
    </div>
    ```

    渲染为:

    ```html
    <div id="example">
    <div>A custom component!</div>
    </div>
    ```

    #### 组件的 template

    组件的 template 必须具有一个根节点,否则,模板编译报错。

    - 可以是内联模板
    - 可以是 script 标签模板
    - 可以是 .vue 模板

    #### 局部注册组件:实例选项的 `components`

    不必在全局注册每个组件。
    通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域中可用:

    ```html
    <body>
    <div id="app">
    <!-- 渲染为 <div>局部组件</div> -->
    <my-component></my-component>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: '#app',
    components: {
    'my-component': {
    template: '<div>局部组件</div>'
    }
    },
    data: {
    },
    })
    </script>
    </body>
    ```

    #### 在 DOM 模板中使用组件注意事项

    当使用 DOM 作为模版时(例如,将 `el` 选项挂载到一个已存在的元素上),
    你会受到 HTML 的一些限制,
    因为 Vue 只有在浏览器解析和标准化 HTML 后才能获取模版内容。
    尤其像这些元素 `<ul>` , `<ol>`, `<table>` , `<select>` 限制了能被它包裹的元素,
    `<option>` 只能出现在其它元素内部。

    在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

    ```html
    <table>
    <my-row>...</my-row>
    </table>
    ```

    自定义组件 `<my-row>` 被认为是无效的内容,因此在渲染的时候会导致错误。
    变通的方案是使用特殊的 `is` 属性:

    ```html
    <table>
    <tr is="my-row"></tr>
    </table>
    ```

    **应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:**

    - `<script type="text/x-template">`
    - JavaScript内联模版字符串
    - `.vue` 组件

    因此,推荐使用字符串模板。

    #### 组件的 `data` 必须是函数

    在组件中,data 必须是函数,下面是错误的方式:

    ```js
    Vue.component('my-component', {
    template: '<span>{{ message }}</span>',
    data: {
    message: 'hello'
    }
    })
    ```

    正确的方式:

    ```js
    data: function () {
    return {
    message: '组件的 data 必须是函数返回一个json字面量对象'
    }
    }
    ```

    ### 组件通信

    - 使用 prop 传递数据
    - props 命名规则
    + camelCase 和 kebab-case
    - 动态 prop
    + v-bind
    - 字面量语法 vs 动态语法
    - 单向数据流

    组件意味着协同工作,通常父子组件会是这样的关系:组件 A 在它的模版中使用了组件 B 。
    它们之间必然需要相互通信:

    - 父组件要给子组件传递数据
    - 子组件需要将它内部发生的事情告知给父组件

    然而,在一个良好定义的接口中尽可能将父子组件解耦是很重要的。
    这保证了每个组件可以在相对隔离的环境中书写和理解,也大幅提高了组件的可维护性和可重用性。

    在 Vue.js 中,父子组件的关系可以总结为 `props down, events up`:

    - 父组件通过 `props` 向下传递数据给子组件
    - 子组件通过 `events` 给父组件发送消息

    ![img/props-events.png](img/props-events.png)

    #### 使用 prop 传递数据

    组件实例的作用域是孤立的。
    这意味着不能(也不应该)在子组件的模板内直接引用父组件的数据。
    要让子组件使用父组件的数据,我们需要通过子组件的props选项。

    子组件要显式地用 `props` 选项声明它期待获得的数据:

    ```js
    Vue.component('child', {
    // 声明 props
    props: ['message'],
    // 就像 data 一样,prop 可以用在模板内
    // 同样也可以在 vm 实例中像 “this.message” 这样使用
    template: '<span>{{ message }}</span>'
    })
    ```

    然后我们可以这样向它传入一个普通字符串:

    ```html
    <child message="hello!"></child>
    ```

    #### camelCase 和 kabab-case 命名规则

    #### 动态 prop

    #### 字面量语法 vs 动态语法

    #### 单向数据流

    prop 是单向绑定的:

    - 当父组件的属性发生变化时,将传导给子组件
    + 子组件动态绑定的 prop,当父组件更新,子组件所有的 prop 都会得到更新
    - 但是不会反过来
    + 也就是说,在子组件内部修改 prop 数据
    * 子组件内部会响应更新
    * 更新不会传导给父组件
    * 同时 Vue 会在控制台发出警告
    * 对象和数组除外
    * 如果 prop 是一个对象或数组,在子组件内部修改它会影响父组件的状态
    * 如果直接给 prop 中的对象或数组类型数据重新赋值,父组件也不会得到更新
    + 这是为了防止子组件无意间修改了父组件的状态
    + 这会让数据流的走向变得混乱而难以理解

    为什么我们会有修改 prop 中数据的冲动呢?

    1. prop 作为初始值传入后,子组件想要把它当作局部数据来用
    2. prop 作为初始值传入后,由子组件处理成其它数据输出

    对于这两种原因,正确的方式是:

    1. 定义一个局部变量,并用 prop 的值初始化它

    ```js
    props: ['initialCounter'],
    data: function () {
    return {
    counter: this.initialCounter
    }
    }
    ```

    2. 定义一个计算属性,处理 prop 的值并返回

    ```js
    props: ['size'],
    computed: {
    normalizedSize: function () {
    return this.size.trim().toLowerCase()
    }
    }
    ```

    #### Prop 验证

    我们可以为组件的 props 指定验证规格。
    如果传入的数据不符合规格,Vue 会发出警告。
    当组件给其他人使用时,这就很有用了。

    要指定验证规格,需要使用对象的形式,而不能用字符串数组:

    ```js
    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
    }
    }
    }
    })
    ```

    `type` 可以是下面原生的数据类型:

    - String
    - Number
    - Boolean
    - Function
    - Object
    - Array

    `type` 也可以是一个自定义构造器函数(例如 Person),
    Vue 会使用 `instanceof` 对数据进行检测。

    当 prop 验证失败,Vue会在抛出警告 (如果使用的是开发版本)。

    ### 自定义事件(父子通信)

    - 使用 v-on 绑定自定义事件
    - 使用自定义事件的表单输入组件
    - 非父子组件通信

    父组件是使用 props 传递数据给子组件,
    但如果子组件要把数据传递回去,应该怎样做?
    那就是自定义事件!

    #### 使用 v-on 绑定自定义事件

    每个 Vue 实例都实现了事件接口:

    - 使用 $on(eventName) 监听事件
    - 使用 $emit(eventName) 触发事件

    父组件可以在使用子组件的地方直接使用 `v-on` 监听子组件发射的事件。
    注意:不能使用 `$on` 侦听子组件抛出的事件,而必须在模板里直接使用 `v-on` 绑定。

    ```html
    <body>
    <div id="app">
    <p>{{ total }}</p>
    <child v-on:increment="incrementTotal"></child>
    <child v-on:increment="incrementTotal"></child>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    Vue.component('child', {
    template: `
    <div>
    <span>{{ counter }}</span>
    <button @click="increment">increment</button>
    </div>`,
    data () {
    return {
    counter: 0
    }
    },
    methods: {
    increment () {
    this.counter += 1
    this.$emit('increment')
    }
    }
    })
    new Vue({
    el: '#app',
    data: {
    total: 0
    },
    methods: {
    incrementTotal () {
    this.total += 1
    }
    }
    })
    </script>
    </body>
    ```

    在本示例中,子组件已经和它外部完全解耦了。
    它所做的只是报告自己的内部事件,至于父组件是否关心则与它无关。

    有时候,可能想要在某个组件的根元素上绑定一个原生事件。
    可以使用 `.native` 修饰 `v-on`。例如:

    ```html
    <my-component v-on:click.native="doTheThing"></my-component>
    ```

    #### 非父子组件通信

    有时候两个组件也需要通信(非父子关系)。
    在简单的场景下,可以使用一个空的 Vue 实例作为中央事件总线:

    ```html
    <body>
    <div id="app">
    <child-a></child-a>
    <child-b></child-b>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    const bus = new Vue()
    Vue.component('child-a', {
    template: `
    <div>
    <p>组件 child A</p>
    <button @click="emitDataToB">发射数据到</button>
    </div>
    `,
    methods: {
    emitDataToB() {
    // 在组件 A 中通过 $emit 发射 data 事件,组件 B 中的钩子监听了 data 事件
    bus.$emit('data', '组件a传递过来的数据')
    }
    }
    })
    Vue.component('child-b', {
    template: `
    <div>
    <p>组件 child B</p>
    <p>{{ message }}</p>
    </div>
    `,
    created() {
    const vm = this
    // 在组件 B 的钩子中通过 bud 的 $on 监听事件
    bus.$on('data', function (data) {
    vm.message = data
    })
    },
    data() {
    return {
    message: 'hello child b'
    }
    }
    })
    new Vue({
    el: '#app',
    data: {},
    })
    </script>
    </body>
    ```

    ### 使用 Slot 分发内容

    - 编译作用域
    - 单个 Slot
    - 具名 Slot
    - 作用域插槽

    在使用组件的时候,我们常常要像这样组合它们:

    ```html
    <app>
    <app-header></app-header>
    <app-footer></app-footer>
    </app>
    ```

    注意两点:

    1. `<app>` 组件不知道它的挂载点会有什么内容。挂载点的内容是由 `<app>` 的父组件决定的
    2. `<app>` 组件很可能有它自己的模板

    为了让组件可以组合,我们需要一种方式来混合父组件的内容和子组件自己的模板。
    这个过程被称为 **内容分发**(或 “transclusion”)。

    Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草案,
    使用特殊的 `<slot>` 元素作为原始内容的插槽。

    #### 编译作用域

    #### 单个 Slot

    - 如果子组件没有 `<slot>` 插口,否则父组件的内容会被丢弃
    - 当子组件模板只有一个没有属性的 slot 时
    + 父组件整个内容片段都将插入到 slot 所在的 DOM 位置
    + 并替换掉 slot 标签本身
    + 在 slot 标签中的任何内容都被视为 备用内容
    + 备用内容在子组件的作用域内编译
    + 并且只有宿主元素为空的时候,备用内容才会被编译显示出来

    示例如下:

    ```html
    <body>
    <div id="app">
    <bs-panel title="面板1">
    面板1的内容
    </bs-panel>
    <bs-panel title="面板2">
    面板2的内容
    </bs-panel>
    <bs-panel title="没有分发内容的面板">
    </bs-panel>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    Vue.component('bs-panel', {
    template: `
    <div class="panel panel-default">
    <div class="panel-heading">
    <h3 class="panel-title">{{ title }}</h3>
    </div>
    <div class="panel-body">
    <slot>
    只有才没有分发的内容时才会显示
    </slot>
    </div>
    </div>
    `,
    props: {
    title: { type: String, required: true }
    }
    })
    new Vue({
    el: '#app',
    data: {},
    })
    </script>
    </body>
    ```

    #### 具名 slot

    - 在组合组件时,内容分发 API 是非常有用的机制
    - `<slot>` 元素可以用一个特殊的属性 `name` 来配置如何分发内容
    - 多个 slot 可以有不同的名字。
    - 具名 slot 将匹配内容片段中有对应 slot 特性的元素
    - 仍然可以有一个匿名 slot,它是默认 slot
    + 作为找不到匹配的内容片段的备用插槽
    + 如果没有默认的 slot,这些找不到插槽的内容片段将被抛弃

    ```html
    <body>
    <div id="app">
    <app-layout>
    <h1 slot="header">顶部</h1>
    <p>内容段落</p>
    <p>内容段落</p>
    <p slot="footer">底部信息</p>
    </app-layout>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    Vue.component('app-layout', {
    template: `
    <div class="container">
    <header>
    <slot name="header"></slot>
    </header>
    <main>
    <slot></slot>
    </main>
    <footer>
    <slot name="footer"></slot>
    </footer>
    </div>
    `
    })
    new Vue({
    el: '#app',
    data: {},
    })
    </script>
    </body>
    ```

    #### 作用域插槽

    - 目的:作用域插槽的目的就是可以将子组件内部的数据传递到外部
    - 在子组件中,在 `slot` 标签上通过属性的方式将 prop 数据传递到外部
    - 在父组件中,通过具有特殊属性 `scope` 的 `<template>` 元素,表示它是作用域插槽的模板
    + `scope` 的值对应一个临时变量名
    + 该变量接收来自子组件中通过 `slot` 元素属性传递的 prop 数据

    示例如下:

    ```html
    <body>
    <div id="app">
    <child>
    <template scope="props">
    <p>hello from parent</p>
    <p>{{ props.text }}</p>
    <p>{{ props.message }}</p>
    </template>
    </child>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    Vue.component('child', {
    template: `
    <div class="child">
    <input v-model="message" />
    <slot text="hello from child" :message="message"></slot>
    </div>
    `,
    data () {
    return {
    message: 'child message'
    }
    }
    })
    new Vue({
    el: '#app',
    data: {
    },
    })
    </script>
    </body>
    ```


    作用域插槽更具代表性的用例是列表组件,允许组件自定义应该如何渲染列表每一项:

    ```html
    <body>
    <div id="app">
    <my-awesome-list :todos="todos">
    <template slot="item" scope="props">
    <li class="my-fancy-item">{{ props.text }}</li>
    </template>
    </my-awesome-list>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    Vue.component('my-awesome-list', {
    props: ['todos'],
    template: `
    <ul>
    <slot name="item"
    v-for="item in todos"
    :text="item.title">
    <!-- fallback content here -->
    </slot>
    </ul>
    `
    })
    new Vue({
    el: '#app',
    data: {
    todos: [
    { id: 1, title: '吃饭' },
    { id: 2, title: '睡觉' },
    { id: 3, title: '打豆豆' },
    ]
    },
    })
    </script>
    </body>
    ```

    ### 动态组件

    通过保留的 `<component>` 元素,动态的绑定到它的 is 特性,
    我们让多个组件可以使用同一个挂载点:

    简单示例

    ```html
    <body>
    <div id="app">
    <select v-model="currentView">
    <option value="home">home</option>
    <option value="posts">posts</option>
    <option value="archive">archive</option>
    </select>
    <component v-bind:is="currentView"></component>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: '#app',
    data: {
    currentView: 'home'
    },
    components: {
    home: {
    template: '<div>home</div>',
    },
    posts: {
    template: '<div>posts</div>',
    },
    archive: {
    template: '<div>archive</div>',
    }
    }
    })
    </script>
    </body>
    ```

    登陆注册示例:

    ```html
    <body>
    <div id="app">
    <ul>
    <li><a href="JavaScript:void(0)" @click="defaultView = 'register'">注册</a></li>
    <li><a href="JavaScript:void(0)" @click="defaultView = 'login'">登陆</a></li>
    </ul>
    <div>
    <component :is="defaultView"></component>
    </div>
    <hr><hr><hr><hr>
    <div>
    <!-- 可以使用 keep-alive 保持组件状态 -->
    <keep-alive>
    <component :is="defaultView"></component>
    </keep-alive>
    </div>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
    new Vue({
    el: '#app',
    components: {
    login: {
    template: `
    <form action="">
    <div>
    <label for="">用户名</label>
    <input type="text" />
    </div>
    <div>
    <label for="">密码</label>
    <input type="password" />
    </div>
    <div>
    <input type="submit" value="点击登陆" />
    </div>
    </form>
    `
    },
    register: {
    template: `
    <form action="">
    <div>
    <label for="">用户名</label>
    <input type="text" />
    </div>
    <div>
    <label for="">密码</label>
    <input type="password" />
    </div>
    <div>
    <label for="">确认密码</label>
    <input type="password" />
    </div>
    <div>
    <label for="">验证码</label>
    <input type="password" />
    </div>
    <div>
    <input type="submit" value="点击注册" />
    </div>
    </form>
    `
    }
    },
    data: {
    defaultView: 'login'
    },
    })
    </script>
    </body>
    ```

    ---

    ## 使用 Vue 的一些经验

    ### 调试 Vue

    - https://github.com/vuejs/vue-devtools
    - https://github.com/MiCottOn/DejaVue


    ### 解决表达式闪烁

    1. 将所有 `{{}}` 通过 `v-text` 替换
    2. 使用 `v-cloak` 解决

    第一,在要渲染处理的 DOM 节点上添加一个指令 `v-cloak`:

    ```html
    <div id="app" ng-cloak>
    {{ message }}
    </div>
    ```

    第二,在 style 中加入一个属性选择器样式:

    ```css
    [v-cloak] {
    display: none;
    }
    ```

    第三,解析执行机制:

    1. 当浏览器解析处理到添加了 `v-cloak` 属性的节点的时候,属性样式被作用上来,也就是说默认这个容器就是隐藏着的
    2. 然后当 Vue 程序解析渲染完HTML模板的时候,自动将容器上的 `v-cloak` 属性移除

  • 相关阅读:
    3月16日
    11月8日
    Code4 APP
    为什么alertView弹出后button会消失的问题
    设置某个类使用或者禁用ARC
    Bundle使用&NSBundle
    respondsToSelector
    NSDate获取当前时区的时间
    iOS enum 定义与使用
    IOS开发之纯代码界面--基本控件使用篇 ┊
  • 原文地址:https://www.cnblogs.com/fly-xfa/p/6864387.html
Copyright © 2011-2022 走看看