vuu2 中的函數式組件

函數式組件

函數式組件(functional component)是一個不持有狀態data、實例this和生命週期的組件。css

函數式組件沒有 data、生命週期和 this,函數式組件又叫無狀態組件(stateless component)。

模板定義:html

<template functional>
  <div>
    <h1>{{ props.title }}</h1>
  </div>
</template>

<script>
  export default {
    name: 'FunOne',
    props: {
      title: [String],
    },
  }
</script>

<style></style>

render 函數定義vue

export default {
  name: 'FunTwo',
  functional: true,
  props: {
    title: [String],
  },
  render(h, { props }) {
    return h('div', {}, [h('h1', {}, props.title)])
  },
}
不能這樣定義:
<template>
  <div>
    <h1>{{ title }}</h1>
  </div>
</template>

<script>
  export default {
    name: 'FunOne',
    functional: true,
    props: {
      title: [String],
    },
  }
</script>

<style></style>
使用 render 函數定義輸入框

MyInput.jsxreact

export default {
  name: 'MyInput',
  functional: true,
  props: {
    value: {
      type: [String, Number],
      default: '',
    },
  },
  // NOTE 函數式組件沒有 this
  render(h, context) {
    const { props, listeners, data } = context
    return h('input', {
      // DOM 屬性
      domProps: {
        value: props.value,
      },
      on: {
        input: ({ target }) => {
          data.on['my-change'](Math.random().toString(36))
          // listeners 是 data.on 的別名
          listeners['my-input'](target.value)
          listeners.input(target.value)
        },
      },
    })
  },
}

在 render 函數中使用 MyInputgit

import MyInput from './MyInput'
export default {
  name: 'MyInputExample',
  data() {
    return { value: '' }
  },
  render(h) {
    // MyInput 是一個組件對象選項
    return h(MyInput, {
      model: {
        value: this.value,
        callback: value => {
          console.log('model', value)
          this.value = value
        },
      },
      on: {
        // NOTE 在父組件的 MyInputExample 上監聽 event-name 事件,
        // 在函數式組件的 listeners 對象
        // 上就會有一個 event-name 方法
        // 用於發送數據到外部
        'my-input': value => {
          console.log('my-input', value)
        },
        'my-change': value => {
          console.log('my-change', value)
        },
      },
    })
  },
}
在父組件的 MyInputExample 上監聽 event-name 事件,在函數式組件的 listeners 對象上 纔會有一個 event-name 方法。

用 render 定義函數式組件

vue 在 render 函數的第二個參數中提供了context,用於訪問 propsslots等屬性:github

props: 組件 props 對象。
data: 組件的數據對象,即 h 的第二個參數。
listeners: 組件上監聽的事件對象,在組件上監聽 `event-name`,listeners 對象就有 `event-name` 屬性,值爲函數,數據可經過該函數的參數拋到父組件。listeners 是 `data.on` 的別名。
slots: 函數,返回了包含全部插槽的對象。
scopedSlots: 對象,每一個屬性爲返回插槽的 VNode 的函數,可傳遞參數。
children:子節點數組,可直接傳入 `h` 函數的第三個參數。
parent: 父組件,可經過它修改父組件的 data 或者調用父組件的方法。
injection:注入對象。

props 和普通組件的 props 同樣,不要求強制,可是聲明後可對其類型進行約束,組件接口也更加清晰。數組

slots() 和 children 的區別?

slots() 返回全部插槽的對象,children 是一個VNode 數組,不包含 template 上的 v-slotbash

')微信

<FunTwo>
  <p slot="left">left</p>
  <span style="color:red;">按鈕</span>
  <template v-slot:right>
    <div>right</div>
  </template>
  <template slot="middle">
    <span>left</span>
  </template>
</FunTwo>

children 中包含pspanspan,不包含 divless

同時提供,由你決定渲染誰。

slots 和 scopedSlots 的區別?

slots 是函數,返回包含全部插槽的 VNode 的對象,屬性爲插槽名字,不能傳參數。

scopedSlots 是對象,屬性爲插槽名,是一個函數,該函數返回對插槽的 VNode,可傳參。

scopedSlots 更增強大。

children、slots、scopedSlots 使用誰?

組件外部使用 v-slot 指定插槽,優先使用 scopedSlots,由於它能夠傳參。

data 對象
<FunTwo
  :class="'fun-com'"
  class="class2"
  :style="{ 'background-color': '#ccc', color, padding: '20px' }"
  style="font-size:20px;"
  :title="title"
  dataKey="title"
  @click="onClick"
/>

包含下列屬性:

on 屬性是一個對象,key 是組件上監聽的事件,on.click(params) 可把 params 發生到父組件,即和this.$emit('click',params) 同樣。

attrs 非 props 屬性。

樣式處理:包含動態的 class 、靜態 staticClass 靜態 staticStyle、動態 style,vue 會把 style 歸一化。

如何在函數式組件中觸發自定義事件?

data.on['event-name'](params) event-name 是組件上監聽的事件名稱,

listeners['event-name'](params).

event-name 是組件上監聽的事件名稱,params 是事件的實參。

父組件監聽不監聽該事件,就沒有該屬性。

injection

父組件提供實例:

provide() {
    return { parent: this }
  },

在子組件中引入:

inject: ['parent'],

injection 就是一個包含parent 屬性的對象了。

不能用 injection.parent.$emit() 觸發自定義事件。&dollar;emit 會在內部綁定 this,而 函數式組件沒有實例。

如何使用 computed 和 methods

函數式組件不是響應式的,不能像普通組件那樣使用計算屬性和方法。

模板定義的函數式組件有一個辦法,直接定義函數,而後在模板中調用。

<template functional>
  <div>
    <h1>{{ props.title }} {{ $options.fullName(props) }}</h1>
  </div>
</template>

<script>
  export default {
    name: 'FunOne',
    props: {
      title: [String],
    },
    fullName(props) {
      return props.title + 'jack' + 'chou'
    },
  }
</script>

render 定義的函數式組件,不能把函數聲明在 props 同級的地方,可在 render 內部聲明。

export default {
  name: 'FunButton',
  functional: true,
  props: {
    title: [String],
  },
  render(h, { data, props, children, slots, scopedSlots, injections, parent }) {
    const fullName = props => {
      return props.title + 'jack' + 'chou'
    }
    return h('div', data, [h('button', {}, fullName(props)), props.title])
  },
}

定義一個函數式組件的 MyButton

MyButton.jsx

export default {
  name: 'MyButton',
  functional: true,
  props: {
    person: {
      type: Object,
      default: () => ({ name: 'jack', age: 23 }),
    },
  },
  render(h, { props, scopedSlots, listeners }) {
    // NOTE default 是關鍵字,須要重命名
    const { left, right, default: _defaultSlot } = scopedSlots
    const defaultSlot = (_defaultSlot && _defaultSlot({ person: props.person })) || <span>按鈕</span>
    const leftSlot = (left && left()) || ''
    const rightSlot = right && right(props.person)
    const button = h(
      'button',
      {
        on: {
          click: () => {
            listeners.click && listeners.click(props.person)
          },
        },
      },
      [defaultSlot]
    )
    return (
      <div>
        {leftSlot}
        {button}
        {rightSlot}
      </div>
    )
  },
}

模板定義方式

<template functional>
  <div>
    <slot name="left"></slot>
    <button @click="listeners['click'] && listeners['click'](props.person)">
      <slot :person="props.person">
        <span>按鈕</span>
      </slot>
    </button>
    <slot name="right" :age="props.age"></slot>
  </div>
</template>

<script>
  export default {
    name: 'MyButton',
    props: {
      person: {
        type: Object,
        default: () => ({ name: '函數式組件', age: 24 }),
      },
    },
  }
</script>

函數式組件有何優點

函數式組件可讀性差,爲什麼還有呢?

快速,即性能好。

函數式組件沒有狀態,也就不須要針對 Vue 反應式系統等額外的初始化了

會對新傳入的 props 等作出反應,但對於組件自身,並不知曉其數據什麼時候改變,由於其並不維護本身的狀態,即沒有 data。

哪一種場景適合使用函數式組件

  1. 純展現的組件,這類組件每每邏輯簡單。
  2. v-for 循環不少的狀況,把這部分代碼提取成函數式組件。
  3. 動態得選擇多個組件中一個來渲染。
  4. 在將 children、props、data 傳遞給子組件以前操做它們 ---- 至關於使用函數式組件做爲其父組件,對其二次封裝
  5. 高階組件(HOC)--- 經過 props 接收一個返回 VNode 的函數,函數的第一個參數爲h,在一個函數式組件中執行該函數,此函數式組件就是高階組件。

高階組件的例子:

export default {
  name: 'Container',
  functional: true,
  render(h, { props }) {
    return props.renderContainer(h, props.data)
  },
}

在模板中使用高階組件:

<template>
  <div class="zm-form-table">
    <ul>
      <li
        v-for="(item, index) in titleList"
        :key="index"
      >
          <Container v-if="typeof item.prop === 'function'" :renderContainer="item.prop" :data="data" />
          <span v-else>
            {{
              ![null, void 0, ''].includes(data[item.prop] &&
                data[item.prop] ||''
            }}
          </span>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
import Container from './container.js'
export default {
  name: 'FormTable',
  components: {
    Container,
  },
  props: {
    titleList: {
      type: Array,
      default: () => {
        return [
          // NOTE 測試數據
          /*
                    { title: '標題3', prop: 'key3' },
                    {
                        title: '標題3',
            // prop 能夠是字符串,也能夠是函數
                        prop: (h, data) => {
                            return (
                                <el-button size='mini' type='primary'>
                                    按鈕
                                </el-button>
                            )
                        },
                    },
          */
        ]
      },
    },
    data: {
      type: Object,
      default: () => {
        return {
          // 測試數據
          /*
                    key1: '測試1',
                    key2: '測試2',
          key3: '測試3',
          */
        }
      },
    },
  },
}
</script>

這樣傳遞 props:

<template>
  <FormTable :titleList="titleList" :data="detail" />
</template>
<script>
  export default {
    name: 'BaseInfo',
    data() {
      return {
        detail: {},
        titleList: [
          {
            title: '家長身份',
            // NOTE data 是從 Container 的 render 函數裏傳入的,prop 返回 VNode 能是組件擴展性更好
            prop: (h, data) => {
              const options = [
                { label: '爸爸', value: 'father' },
                { label: '媽媽', value: 'mother' },
                { label: '爺爺', value: 'grandpa' },
                { label: '奶奶', value: 'grandma' },
                { label: '外公', value: 'grandfather' },
                { label: '外婆', value: 'grandmother' },
                { label: '其餘', value: 'other' },
              ]
              const identity = options.find(item => data[key] === item.value)
              return <span>{identity && identity.label}</span>
            },
          },
          {
            title: '家長微信',
            prop: 'weiXin',
          },
        ],
      }
    },
    created() {
      setTimeout(() => {
        // 接口返回
        this.detail = {
          identity: 'father',
          weiXin: 'jack8848',
        }
      }, 1000)
    },
  }
</script>

把 render 函數經過 props 傳入組件,這種模式極爲有用,在稍後的組件封裝中可看到它的好處。

react 中高階組件:組件做爲入參,新組件做爲返回值的函數,在返回以前,能夠作一些其餘處理,作其餘處理纔是高階組件的目的。vue 也能實現這樣的組件,可是有點麻煩了。
可參考這裏: 探索 Vue 高階組件

函數式組件的問題

  1. 樣式的 scoped 在函數式組件和狀態組件表現不一樣。
scoped 樣式的函數式組件把 scoped 樣式的函數式組件做爲子組件,css 選擇器相同,父組件的樣式生效,即子組件的 scoped 沒有生效。

參考

Scoped styles inconsistent between functional and stateful components

Renderless Components in Vue.js

相關文章
相關標籤/搜索