搞懂并学会运用 Vue 中的无状态组件

做者:Milos Proticcss

译者:前端小智html

来源:medium前端


阿里云最近在作活动,低至2折,真心以为很划算了,能够点击本条内容或者连接进行参与promotion.aliyun.com/ntms/yunpar…vue

腾讯云最近在作活动,百款云产品低至 1 折,能够点击本条内容或者连接进行参与node


啥是应用程序状态,为何我们须要它?

状态管理一般在较小的项目并不须要,可是当涉及到更大的范围时,如企业级的应用大部分须要它了。简单的说,状态是一个包含应用程序使用的最新值的对象。可是,若是我们从结构的、更抽象的角度来看待它,就会清楚地看到,状态是复杂应该中重要一块,它使可以构建干净的体系结构,并将关注点强有力地分离开来。git

一般,缺少经验的开发人员没法预测对状态管理的需求,以及如何实现状态管理,所以很难了解状态管理的重要性。若是基于状态的组件堆积起来,它们之间的数据管理和共享将成为一场噩梦。从长远来看,拥有的基于状态的组件越多,出现的问题就越多。github

若是没有使用外部包进行状态管理,那么最好尽量少地使用基于状态的组件,而展现组件则使用围绕它们构建的状态。编程

Vue 和无状态(函数)组件

Vue 中的无状态组件其实就是函数组件。但函数组件又是啥呢? 要回答这个问题,我们首先必须理解什么是函数式编程。数组

与将程序分解为对象的面向对象方法不一样,函数式编程鼓励将程序分解为小函数,这些小函数用于造成更高级的程序。咱们建立的函数不依赖于或能够改变任何外部状态,这致使另外一个观察结果,对于给定的输入,它们老是返回相同的输出。微信

所以,函数组件是没有状态的组件,而且能够更改它。函数组件输出老是基于给定的输入。在 Vue 方面,这类组件会根据给定的props给出不一样的输出。

语法

Vue 提供了一种定义函数组件的简单方法。我们只须要给个 functional 关键字就能够。在 2.5.0 及以上版本中,若是使用了单文件组件,那么基于模板的函数式组件能够这样声明::

<template functional>
  <div> 函数/无状态组件 </div>
</template>
复制代码

或者

export default {
  functional: true,
  props: {
    // ...
  },
  render(createElement, context) {
    return createElement(
      'div', '函数/无状态组件'
    )
  }
}
复制代码

注意:在 2.3.0 以前的版本中,若是一个函数式组件想要接收 prop,则 props 选项是必须的。在 2.3.0 或以上的版本中,你能够省略 props 选项,全部组件上的特性都会被自动隐式解析为 prop

当使用函数式组件时,该引用将会是 HTMLElement,由于他们是无状态的也是无实例的。

须要注意的是,传递给函数组件的唯一数据是props。这些组件是彻底无状态的(没有响应数据),它们忽略传递给它们的任何状态,而且不触发任何生命周期方法(createdmounted等等)。

并且,我们也不能经过使用 this 关键字来访问实例,由于这些组件也是不实例化的。相反,组件须要的全部东西都是经过context提供的。在render函数中,它做为createElement方法的第二个参数传递。

组件须要的一切都是经过 context 参数传递,它是一个包括以下字段的对象:

  • props:提供全部 prop 的对象

  • children: VNode 子节点的数组

  • slots: 一个函数,返回了包含全部插槽的对象

  • scopedSlots: (2.6.0+) 一个暴露传入的做用域插槽的对象。也以函数形式暴露普通插槽。

  • data:传递给组件的整个数据对象,做为 createElement 的第二个参数传入组件

  • parent:对父组件的引用

  • listeners: (2.3.0+) 一个包含了全部父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名。

  • injections: (2.3.0+) 若是使用了 inject 选项,则该对象包含了应当被注入的属性。

为何我们须要无状态组件

到目前为止,我们已经了解到函数组件是无状态的,在它们的核心中,它们只是可执行的函数,接受一些输入并根据其提供输出。

就它们的用法而言,由于函数式组件只是函数,因此渲染开销也低不少,这也意味着它们是很是高效的,不须要花太多时间渲染。同时,考虑高阶组件,它们不须要任何状态,它们所要作的就是用额外的逻辑或样式包装给定的子组件。

接下来,通例事例展现同样啥时使用函数组件,函数组件很是适合此类任务。

实例

在这个示例中,我们建立一个panel组件,它充当一个包装器,并提供所需的样式。子组件将在panel 主体中渲染:

export default {
  name: 'panel',
  functional: true,
  props: {
    title: String
  },
  render(createElement, context) {
    const slots = context.slots();

    const header = createElement('header', {
      attrs: { class: 'panel-header'}
    }, context.props.title);
    
    const body = createElement('main', {
      attrs: { class: 'panel-body'}
    }, slots.default);

    return createElement('section', {
      attrs: { class: 'panel' }
    }, [header, body]);
  }
}
复制代码

如上所述,此组件的惟一目的是提供相似于面板(卡片)的样式,它有headermain元素,分别保存面板标题和HTML内容。整个过程是经过使用render函数中的createElement参数在中完成。createElementVue 核心中实现的虚拟 Dom 系统的一部分。

虚拟 DOM

Vue 经过创建一个虚拟 DOM 来追踪本身要如何改变真实 DOM。请仔细看这行代码:

return createElement('h1', this.blogTitle)
复制代码

createElement 到底会返回什么呢?其实不是一个实际的 DOM 元素。它更准确的名字多是 createNodeDescription,由于它所包含的信息会告诉 Vue 页面上须要渲染什么样的节点,包括及其子节点的描述信息。咱们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是咱们对由 Vue 组件树创建起来的整个 VNode 树的称呼。

createElement 参数

接下来你须要熟悉的是如何在 createElement 函数中使用模板中的那些功能。这里是 createElement 接受的参数:

// @returns {VNode}
createElement(
  // {String | Object | Function}
  // 一个 HTML 标签名、组件选项对象,或者
  // resolve 了上述任何一种的一个 async 函数。必填项。
  'div',

  // {Object}
  // 一个与模板中属性对应的数据对象。可选。
  {
    // (详情见下一节)
  },

  // {String | Array}
  // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
  // 也可使用字符串来生成“文本虚拟节点”。可选。
  [
    '先写一些文字',
    createElement('h1', '一则头条'),
    createElement(MyComponent, {
      props: {
        someProp: 'foobar'
      }
    })
  ]
)
复制代码

面板 CSS 样式以下:

.panel {
  margin-bottom: .5rem
}

.panel, .panel-header {
    border: 1px solid #d3d3d3;
    border-radius: 4px;
}

.panel-header, .panel-body, .panel {
  padding: .5rem;
}

.panel-header {
  background-color:#efefef;
  color: #eeeee
}
复制代码

这是一个简单直接的 CSS,提供了一些paddingcolor

子组件

如今,为了让例子更加生动为此,我们再建立两个附加组件,一个显示汽车列表,另外一个只是一个简单lorem-ipsum的文本组件,要求它们具备相同的面板样式和外观。

列表组件:

export default {
  name: 'cars',
  props: {
    data: Array
  }
}
复制代码

template:

<template>
  <ul>
    <li v-for="car in data" :key="car">{{car}}</li>
  </ul>
</template>
复制代码

文本组件:

export default {
  name: 'lorem-ipsum'
}
复制代码

template:

<template>
  <p>
   终身学习者,终身学习者,终身学习者,终身学习者,终身学习者
  </p>
</template>
复制代码

如今,有了可用的子组件,我们所须要作的就是用panel组件将它们封装到应用程序中,以下所示:

<div class="vue-app">
  <panel :title="'Car Manufacturers'">
    <cars :data="['Mazda', 'Ford', 'Mercedes']"></cars>
  </panel>
  <panel :title="'Lorem Ipsum'">
    <lorem-ipsum></lorem-ipsum>
  </panel>
</div>
复制代码

请注意,使用这些组件是由于示例比较简单。在实际应用中,它能够是任何类型的组件。

完整代码

hmtl

<div class="vue-app">
  <panel :title="'Car Manufacturers'">
    <cars :data="['Mazda', 'Ford', 'Mercedes']"></cars>
  </panel>
  <panel :title="'Lorem Ipsum'">
    <lorem-ipsum></lorem-ipsum>
  </panel>
</div>

<script type="text/x-template" id="cars">
  <template>
    <ul>
      <li v-for="car in data" :key="car">{{car}}</li>
    </ul>
  </template>
</script>

<script type="text/x-template" id="lorem-ipsum">
  <template>
    <p>前端小智, 终身学习者,终身学习者,终身学习者,终身学习者,终身学习者</p>
  </template>
</script>
复制代码

css

body {
  padding: .5rem
}

* {
  padding: 0;
  margin:0;
  box-sizing: border-box;
}

.panel {
  margin-bottom: .5rem
}

.panel, .panel-header {
    border: 1px solid #d3d3d3;
    border-radius: 4px;
}

.panel-header, .panel-body, .panel {
  padding: .5rem;
}

.panel-header {
  background-color:#efefef;
  color: #eeeee
}

ul {
  list-style: none;
}

ul > li {
  padding: .5rem .2rem
}
复制代码

js

// the wrapper panel
const panel = {
  functional: true,
  name: "panel",
  props: {
    title: String
  },
  render(createElement, context) {
    const slots = context.slots();

    const header = createElement('header', {
      attrs: { class: 'panel-header'}
    }, context.props.title);
    
    const body = createElement('main', {
      attrs: { class: 'panel-body'}
    }, slots.default);

    return createElement('section', {
      attrs: { class: 'panel' }
    }, [header, body]);
  }
}

// sample components

const cars = {
  name: 'cars',
  template: '#cars',
  props: {
    data: Array
  }
}

const loremIpsum = {
  name: 'lorem-ipsum',
  template: '#lorem-ipsum'
}

new Vue({
  el: '.vue-app',
  components: {
    panel,
    cars,
    'lorem-ipsum': loremIpsum
  }
});
复制代码

运行效果:


代码部署后可能存在的BUG无法实时知道,过后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给你们推荐一个好用的BUG监控工具 Fundebug

原文:itnext.io/whats-the-d…


交流

干货系列文章汇总以下,以为不错点个Star,欢迎 加群 互相学习。

github.com/qq449245884…

由于篇幅的限制,今天的分享只到这里。若是你们想了解更多的内容的话,能够去扫一扫每篇文章最下面的二维码,而后关注我们的微信公众号,了解更多的资讯和有价值的内容。

clipboard.png

每次整理文章,通常都到2点才睡觉,一周4次左右,挺苦的,还望支持,给点鼓励

相关文章
相关标签/搜索