# 渲染函式和 JSX

# 渲染函式

如果需要在 JavaScript 中建立 template 的內容時,可以使用 渲染函式,並結合 render 函式。

以渲染 h1 ~ h6 的範例,我們只能透過 propslevel 傳入,並動態生成各個標題時,一般會以這樣實現:

<script type="text/x-template" id="anchored-heading-template">
  <h1 v-if="level === 1">
    <slot></slot>
  </h1>
  <h2 v-else-if="level === 2">
    <slot></slot>
  </h2>
  <h3 v-else-if="level === 3">
    <slot></slot>
  </h3>
  <h4 v-else-if="level === 4">
    <slot></slot>
  </h4>
  <h5 v-else-if="level === 5">
    <slot></slot>
  </h5>
  <h6 v-else-if="level === 6">
    <slot></slot>
  </h6>
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Vue.component('anchored-heading', {
  template: '#anchored-heading-template',
  props: {
    level: {
      type: Number,
      required: true,
    },
  },
});
1
2
3
4
5
6
7
8
9

但這不是一個好方法,除了程式碼冗長外,也在需要有插槽的位置,重複寫了 <slot></slot>

如果使用 render 函式來撰寫上面的範例,會像這樣:


 
 
 
 
 









Vue.component('anchored-heading', {
  render: function(createElement) {
    return createElement(
      'h' + this.level, // 標籤名稱
      this.$slots.default // 相當於 <slot></slot>
    );
  },
  props: {
    level: {
      type: Number,
      required: true,
    },
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
試一試

Hello world!

# 虛擬 DOM

Vue 透過建立一個虛擬的 DOM 來追蹤自己要如何改變真實的 DOM。

return createElement('h1', this.blogTitle);
1

createElement 其實不是一個 實際的 DOM 元素。更準確的名字可能是 createNodeDescription,因為它所包含的內容可以告訴 Vue 的畫面上需要渲染什麼樣的節點,包括再下一層的子節點。而這樣的節點描述也可以稱為 虛擬節點(virtual node),也簡稱為 VNode。由 VNode 建立起的節點樹可以稱為 虛擬 DOM

# createElement 參數

可以參考官網 createElement 中可以允許的 參數 (opens new window)

# 重複多個元素或組件

可以使用下方的方式達成:

render(createElement) {
  return createElement(
    "ul",
    Array.apply(null, { length: this.count }).map((item, index) =>
      createElement("li", `Count ${index + 1}`)
    )
  );
}
1
2
3
4
5
6
7
8
試一試
  • Count 1
  • Count 2
  • Count 3

# 使用 JavaScript 代替 <template> 功能

# v-if 和 v-for

我們要判斷是否資料有內容,如果沒有則顯示額外的文字,一般會這樣寫:

<ul v-if="items.length">
  <li v-for="item in items">{{ item.name }}</li>
</ul>
<p v-else>No items found.</p>
1
2
3
4

而使用渲染函式,可以透過 JavaScript 的 ifelsemap 來達成:

render(createElement) {
  if (this.list.length) {
    return createElement(
      "ul",
      this.list.map((item) => {
        return createElement("li", item);
      })
    );
  } else return createElement("p", "No fruit was selected.");
}
1
2
3
4
5
6
7
8
9
10
試一試

Select

Fruit basket
  • Apple
  • Banana

# JSX

安裝方式請看 官方說明 (opens new window)

提醒

如果在父組件中引用子組件,並將 styleclass 傳入時,這兩個屬性可以在 context.data 查找,但它們不會被歸類至 context.data.attrs 屬性中。

<div class="parent">
  <ChildComponent name="parentName" class="pb-0 border-bottom-0" />
</div>
1
2
3

子組件的 data 屬性:



 


{
  attrs: {name: "parentName"},
  class: 'pb-0 border-bottom-0'
}
1
2
3
4

# 多元素渲染

使用 Javascript 的 Array.map 來達成。

子組件:

const ChildComponent = {
  // 不使用生命週期
  functional: true,
  props: {
    name: String,
    age: Number,
  },
  render(createElement, { props }) {
    return (
      <li>
        Name:{props.name},Age:{props.age}
      </li>
    );
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

父組件:

<template> 改由 render function 產生。






 
 
 






export default {
  render() {
    return (
      <section class="jsxMap">
        <ul>
          {this.list.map((o) => (
            <ChildComponent key={o.name} {...{ props: o }} />
          ))}
        </ul>
      </section>
    );
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13

# functional event

透過 context.listeners 屬性,將 custom event 傳入子組件的事件中。



 





const ChildComponent = ({ data, props, listeners }) => (
  <div class="childComponent">
    <p class={data.class} onClick={listeners.pipi}>
      Hi, {props.name}
    </p>
  </div>
);
1
2
3
4
5
6
7







 






export default {
  render() {
    return (
      <div>
        <ChildComponent
          class="text-success"
          name="John Doe"
          onPipi={this.clickHandler}
        />
      </div>
    );
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
試一試

Click the below title.

Hi, John Doe

# v-model

直接於渲染的組件中使用 v-model 的屬性綁定。










 










render() {
  return (
    <div>
      {this.checkboxs.map((checkbox) => {
        return (
          <input
            type="checkbox"
            id={checkbox.name}
            value={checkbox.name}
            v-model={this.checkedList}
          />
          <label for={checkbox.name}>
            {checkbox.name}
          </label>
        );
      })}
    </div>
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
試一試

Checked items:[]

# conditional render

依照 props 接收的狀態,來決定渲染的內容。





 
 



const PermissionComponent = {
  functional: true,
  render(h, { props, slots }) {
    const { permissionCode } = props;
    if (permissionCode === 0) return null;
    return slots().default;
  },
};
1
2
3
4
5
6
7
8
export default {
  data() {
    return {
      permissionCode: 1,
    };
  },
  render() {
    return (
      <section>
        <PermissionComponent permissionCode={this.permissionCode}>
          <h2>{`PermissionCode${this.permissionCode}`}</h2>
        </PermissionComponent>
      </section>
    );
  },
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 功能性組件

沒有管理任何狀態,也沒有監聽任何傳給他的狀態,也沒有生命週期方法。實際上,它只是一個接收一些 props 的參數。我們可以將組件增加 functional 屬性,代表這個組件是 沒有響應式狀態沒有 this 可以使用。


 












Vue.component('my-component', {
  functional: true,
  // 可以選擇是否寫
  // 因為在 2.3.0 以上的版本,會自動將組件上的 attribute 隱含轉為 props
  props: {
    // ...
  },
  // 為了彌補缺少的組件實體
  // 提供第二個參數作為組件內的溝通
  render: function(createElement, context) {
    // ...
  },
});
1
2
3
4
5
6
7
8
9
10
11
12
13

提醒

當使用功能性組件時,引用的將會是 HTMLElement,因為它們無狀態和無實體的。

# context

組件需要的一切都是透過 context 參數傳遞。

<child-component
  name="parentName"
  id="parentId"
  @parent-event="parentEventHandler"
>
  <template #header>This's from parent header</template>
  <template>This's from parent default</template>
  <template #footer>This's from parent footer</template>
</child-component>
1
2
3
4
5
6
7
8
9
const childComponent = {
  functional: true,
  data() {
    return {
      name: 'childName',
      id: 'childId'
    };
  },
  render(h, { props, data, children, slots, listeners }) {
    return (
      ...
    );
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14

常用的屬性有:

  • props:提供所有 props 的狀態
  • children:slots().default 的 VNode 節點內容
  • slots:一個函式,返回了包含所有的插槽
  • data:傳遞給組件的整個狀態
  • listeners:包含所有父組件為當前組件註冊的事件監聽
試一試

Open the devTool to watch context

  • props.name:parentName
  • props.id:parentId

slots().header:This's from parent header

slots().default: This's from parent default

slots().footer:This's from parent footer

Last Updated: 2/25/2021, 7:56:51 AM