React-(四)組件進階

用 props 指定初始 state

  • 在使用組件的 tag 中,寫一個 props ,其初始值為 10 的 initCount

    1
    2
    3
    4
    // index.js

    // import... 略
    ReactDom.render(<Counter initCount={10}/>,document.getElementById('root'));
  • 第 5 行,使用建構子(constructor)來取代直接寫 state,其傳入 props 參數

  • 第 6 行的 super 指的是繼承自的父層”Compoment”
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //Counter.js

    //import... 略
    class Counter extends Component{
    constructor(props){
    super(props);
    this.state = {
    count:props.initCount,
    }
    }
    }

預設 props 值

不過,假設使用組件時忘了給 props,就會出現 NaN 的錯誤

  • 第 4 行,在使用 Counter 組件時,沒有給 initCount,所以在建構子函式中,會取得不到 props,導致畫面出現 NaN 錯誤
    1
    2
    3
    4
    //index.js

    //import... 略
    Reactdom.render(<Counter/>,document.getElementById('root'))

為了避免在使用組件時,上述忘了給 props 的情況,必須給予 props 預設值

方法一:使用 static defaultProps 預設 props 值

1
2
3
4
5
6
7
8
9
// Counter.js

//import... 略
class Counter extends Component{
static defaultProps = {
initCount :0
}
// ...略
}

方法二:用 物件.default 預設 props 值

1
2
3
4
5
6
7
8
9
10
//Counter.js

//import... 略
class Counter extends Component{
// ...略
}

Counter.default{
initCount:0
}

用 propTypes 檢查 props 型別

  • 在使用組件的 tag 中,寫一個 props ,其初始值為 “0”(字串) 的 initCount

    1
    2
    3
    4
    // index.js

    // import... 略
    ReactDom.render(<Counter initCount="0"/>,document.getElementById('root'));
  • 寫了一個方法,讓 count + 1,原先預期初始值是 0,執行動作後會變成 1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // Counter.js

    //import... 略
    class Counter extends Component{
    // ...略
    addCount = ()=>{
    this.setState({
    count:this.state.count +1;
    })
    }
    }
  • 可是實際執行結果卻變成”01”,因為一開始傳進來的 props 型態為字串 “0” 而非數字 0,所以導致錯誤

為了避免因為型態錯誤,導致後續組件的處理出錯,需要預先定義好 props 的型態

方法一:使用 static default 預設 props 值

  • PropTypes 本來是 React 內建的模組,可是後來被分割獨立,所以第 4 行,要單獨引入 PropTypes
  • 第 8 行定義 initCount 的型別要是數字型態
1
2
3
4
5
6
7
8
9
10
11
// Counter.js

//import... 略
import PropTypes from 'prop-types';

class Counter extends Component{
static propTypes = {
initCount :PropTypes.number,
}
// ...略
}

方法二:使用 物件.propTypes 檢查 props 型別

  • PropTypes 本來是 React 內建的模組,可是後來被分割獨立,所以第 4 行,要單獨引入 PropTypes
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //Counter.js

    //import... 略
    import PropTypes from 'prop-types';

    class Counter extends Component{
    // ...略
    }

    Counter.propTypes{
    initCount:PropTypes.number,
    }

React-(三)組件基本

組件狀態:state 與 setState

state

  • 新增一個「Message.js」檔案
  • 第5行,定義一個 state,本身是一個物件
    • 其中有一個 text,並給予預設值”Hi”
  • 第11行,將要 render 出來的東西用{}包著

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Message.js
    import React, { Component } from 'react';

    class Message extends Component {
    state = {
    text: "Hi",
    }
    render() {
    return (
    <div>
    <h3>{this.state.text}</h3>
    </div>
    );
    }
    }

    export default Message;
  • 將 Message 組件掛載到 DOM 元素中

    1
    2
    3
    4
    5
    6
    //index.js
    import React from 'react';
    import ReactDom from 'react-dom'
    import Message from './Message'

    ReactDom.render( <Message/> , document.getElementById('root'));
  • 執行指令

    1
    yarn start
  • 可以看到瀏覽器中的畫面, 顯示 text 的初始值”Hi”

setState

  • 第7行,自定義一個 sayHi 方法
  • 第16行,在 button 上面綁定 onClick 事件(JSX的事件要用駝峰命名),並且觸發 sayHi 方法

在箭頭函式中的 this 代表被宣告時的 context,也因此在組件中的方法若用箭頭函式來宣告,其中的 this 就一定會代表組件本身

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React, { Component } from 'react';

class Message extends Component {
state = {
text: "Hi",
}
sayHi = () => {
this.setState({
text: "Hi"
})
}
render() {
return (
<div>
<h3>{this.state.text}</h3>
<button onClick={this.sayHi}>Say Hi</button>
</div>
);
}
}

export default Message;

setState的參數要傳入一個物件是一個 partial state(部分的狀態),也就是說不需要傳入完整的state

  • 下方案例的 state 有”title” 跟 “message”
  • 可是在 setState 時,只需要把部分待改變的狀態傳進去,因此只傳 title 就只會改變該值的狀態
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Message extends Component{
    state = {
    title:"",
    message:""
    },
    sayHi = ()=>{
    this.setState({
    title:"Hello"
    });
    }
    }

組件的自訂函式

在組件中的自訂函式,有兩種方法宣告:

  • 箭頭函式:可以確保 this 代表組件本身
    • 第 17 行跟第 21 行執行 sayHi 函式時,因為 sayHi 函式以箭頭函式的形式宣告,因此其中的 this 都代表該函式被宣告時的 context,也就是 Message 物件
  • 一般函式:有自己的context ,因此 this 會依據不同的情況而有所不同
    • 第 18 行,用此方法執行 sayHello 函式,則 sayHello 函式中的 this 代表執行時的 this (第14行的 this)
    • 第 22 行,函式綁定在 button 事件上,則 sayHello 函式中的 this 就會代表 button 元素本身,如此一來就會出錯
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      //箭頭函式
      class Message extends Component{
      // ...略
      sayHi = () => {
      this.setState({
      text: "Hi"
      })
      }
      //一般函式
      sayHello(){
      this.setState({
      text: "Hello"
      })
      }
      render() {
      this.sayHi();
      this.sayHello();//
      return (
      <div>
      <button onClick={this.sayHi}>Say Hi</button>
      <button onClick={this.sayHello}>Say Hello</button>
      </div>
      );
      }
      }

為了避免函式中的 this 指向出錯,可以透過在建構函式(Construstor)中,去重新綁定函式的 context

  • bind 是函式的方法,每一個(自訂)函式都可以有 bind 這個方法
  • bind 方法傳入的參數,代表該函式要綁定的 context(不論用什麼方式執行該函式,它的 context 都會固定是該被傳入的參數)
  • 第 5 行,bind(this)的 this,代表 Message 組件,所以把組件傳入定義該函式綁定的 context
  • 第 5 行,this.sayHello.bind(this)會回傳出一個函式,並將該函式 assign 回原先的this.sayHello
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Message extends Component{
    // ...略
    constructor(props){
    super(props);
    this.sayHello = this.sayHello.bind(this);
    }
    sayHello(){
    this.setState({
    text: "Hello"
    })
    }
    render() {
    this.sayHello();
    return (
    <div>
    <button onClick={this.sayHello}>Say Hello</button>
    </div>
    );
    }
    }

props:上面傳下來的屬性

如果父組件要傳值給子組件,可以透過 props 傳遞

  • 在父組件使用子組件的 tag 中,可以自訂 props 傳值
  • 如果 props 的值非字串,則要用 {} 括起來
  • 如果是直接在 opening tag 跟 closing tag 之間的內容,則子組件要用 this.props.children 存取

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //List.js(父組件)
    // ...略
    import Item from "./Item";
    class List extends Component{
    render(){
    return (
    <ol>
    <Item text="apple" count={2}>On stock</Item>
    <Item text="orange" count={1}>On stock</Item>
    <Item text="green" count={3}>On stock</Item>
    <Item text="blue" count={0}>Sold out</Item>
    </ol>
    )
    }
    }
    // ...略
  • 從父組件中傳來的 props,會存為子組件的 props,因此可以使用 this.props.xxx 呼叫出來

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //Item.js(子組件)
    // ...略

    class Item extends Component{
    render(){
    return (
    <li>{this.props.text}:{this.props.count}/{this.props.children}</li>
    )
    }
    }
    // ...略

React-(二)JSX與render

JSX語法:用JS寫HTML

解析 JSX 語法

  • 首先「import」引入 React 庫

    1
    import React,{ Component } from 'react';
  • 「App」是自己定義的 React 組件

  • React 組件可以自己用 Class 來定義,但是要 「extends(繼承)」自 React 的 「Component」
  • 「Component」是 React 定義好的 Class

    1
    2
    3
    4
    5
    6
    7
    8
    class App extends Component{
    // ...略
    }

    //或者是寫
    class App extends React.Component{
    // ...略
    }
  • 每一個組件,都會有一個「render」函式

  • 「render」函式裡面會 return 一個 JSX 物件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class App extends Component{
    render(){
    return (
    <div className="App">
    <header className="App-header">
    // ...略
    </header>
    </div>
    );
    }
    }

JSX 物件:是 React 發明要讓我們在 React 的 JS 程式中,可以快速定義 HTML 的模板

JSX 跟 HTML 的差異

  1. JSX ㄧ定要 close(以”/“作為tag結束)

    • 起始標籤自己結尾(self colse)
    • 起始標籤(opening tag)+結束標籤(closing tag)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <!-- JSX -->
      <img alt="logo" />
      <input type="test"/>
      <p>Hello</p>

      <!-- HTML不一定要clsoe(不加"/") -->
      <img alt="logo" >
      <input type="test">

      ```
      2. JSX 的所有標籤都可 self colse
      ```htmlmixed=
      <!-- JSX -->
      <div/>
      <div></div>

      <!-- HTML -->
      <div></div>
  2. “className” 取代 “class”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <!-- JSX -->
    <div className=""/>

    <!-- HTML -->
    <div class=""></div>
    ```
    4. "htmlFor" 取代 "for"
    ```htmlmixed=
    <!-- JSX -->
    <input type="text" id="checkId"/>
    <label htmlFor="checkId"></label>

    <!-- HTML -->
    <input type="text" id="checkId"/>
    <label for="checkId"></label>
  3. event 要用駝峰式命名

    1
    2
    3
    <!-- JSX -->
    <input type="text" onChange=""/>
    <button onClick=""></button>
  4. 用”{}”框住一個值或函式

    1
    2
    <!-- JSX -->
    <button onClick={this.onClick}>{this.state.count}</button>

入門組件

先在 「src」 資料夾中,新增兩個檔案,分別是 「Item.js」 跟 「List.js」,接下來的流程會分別製作這兩個組件,並且在最後於「List.js」引入「Item.js」

  • 新增一個「Item.js」檔案
  • 第2行,引入 React
  • 第4行,定義一個新的class,命名為 Item ,繼承自 React 的 Component
  • 第5行,使用 render 函式,其中 return 該物件的 html(用JSX語法寫html)
  • 第12行,最後 export 這個物件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //Item.js
    import React from 'react';

    class Item extends React.Component{
    render(){
    return (
    <li>Hello</li>
    )
    }
    }

    export default Item;
  • 新增一個「List.js」檔案

  • 第2行,引入 React,以及在 React 中的 Component
  • 第3行,使用相對路徑,引入另一個物件「Item」(在”./Item.js”路徑中,可以省略副檔名”.js”),
  • 第5行,定義一個新的class,命名為 List ,繼承自 React 的 Component
  • 第6行,使用 render 函式,其中 return 該物件的 html(用JSX語法寫html)
  • 第18行,最後 export 這個物件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //List.js
    import React,{Component} from 'react';
    import Item from './Item';

    class List extends Component{
    render(){
    return(
    <ol>
    <Item/>
    <Item/>
    <Item/>
    <Item/>
    </ol>
    )
    }
    }

    export default List;

render:把組件畫上頁面

如果使用create-react-app指令,其 webpack 打包的入口為 「src」資料夾中的「index.js」檔案。意思是,webpack 在執行打包編譯時,會先找到「index.js」檔案,並且依序找到其中 import 的模組,接著再一層接一層去找「模組中 import 的模組」…。

ReactDom 跟 React 是兩個不同的東西,分屬於兩個不同的 package,React 是核心,而 ReactDom 是把 React 掛載到 DOM 上使用。

  • 新增一個 「index.js」 檔案
  • 第2行,因為會使用 JSX,所以import React,讓程式碼在 React 的 Scope 中
  • 第3行,import ReactDom
  • 第4行,import 定義好的組件
  • 第7行,透過 ReactDom.render 函式有兩個參數
    • 第一個參數是 React Element(補充:被 import 的 List 是一個組件,但是如果用<>括起來,<List/>就代表是一個 Element )
    • 第二個參數是 DOM Container(一個 id 為’root’的 DOM元素)
      1
      2
      3
      4
      5
      6
      7
      //index.js
      import React from 'react';
      import ReactDom from 'react-dom';
      import List from './List';


      ReactDom.render(<List/>,document.getElementById('root'));

打開「public」資料夾中的「index.html」,可以看到 id 為’root’的 DOM元素就是我們上面指定的 DOM Container。意義就是,讓 <List/> 元素放到 id 為 ‘root’ 的 DOM 元素中

接著打開 termianl,使用指令將專案開啟於瀏覽器上

1
yarn start


React-(一)創建一個React專案(懶人包)

快速建立一個React專案

利用create-react-app套件(README) ,快速產生webpack的設定檔

1
npm install global create-react-app

接著輸入 yarn 初始化指令(若習慣用npm也可以使用npm指令),創建一個名為”my-app”的專案

1
create-react-app my-app

初始化完成後,會產生一個”my-app”資料夾

進入新創建的專案中,使用 yarn 執行指令

1
2
cd my-app
yarn start

成功打開瀏覽器,顯示react測試網頁畫面

一個簡易的 react 專案創建完成,可以試著在my-app/src/App.js中作修改,並查看瀏覽器畫面的變化

create-react-app套件初始化的 React 專案,是使用 Webpack 掛載babel,將 JSX 語法轉化為瀏覽器可以解析的 Javascript 程式碼

function 與 class 的差別

Vue-EventBus組件溝通

使用情境

在 Vue 框架的核心概念中,會盡可能將資料數據與 UI 封裝成可重複使用的組件,然而,當涉及組件與組件之間的資料交互傳輸時,就需要用到組件溝通的工具。

一般常用的父、子層組件溝通,會使用 props 、 $emit 來傳收資料;可是多個組件需要共用一種狀態時,就會需要使用到全域狀態管理的 Vuex。

可是有時候情況只是兄、弟層組件溝通,沒有那麼複雜需要用到 Vuex ,就可以使用 EventBus。

參考延伸說明: Props使用方式Props/)、Vuex使用時機%E7%8B%80%E6%85%8B%E7%AE%A1%E7%90%86vuex(1:2)/)

EventBus簡介

在 Vue 中使用 EventBus,就像是所有組件共同的事件中心,組件與組件之間可以上下垂直、左右平行互相溝通。

具體做法是在專案入口檔案,建立一個 Vue 實例(Instance) 作為 EventBus,接著在各個組件之中去引入這個 EventBus,透過分別調用 EventBus 的事件監聽來完成組件之間的資料傳遞。

實作

初始化 EventBus

首先要創建一個 EventBus,有兩種方式:

  1. 第一種,直接在 main.js 檔案中初始化,目的是要在 Vue 的原型下掛載 EventBus。
1
2
//main.js
Vue.prototype.$EventBus = new Vue();
  1. 第二種,新增一個 eventBus.js 檔案,在其中引入 Vue,並且匯出宣告出來的 Vue 實例。實際上,它是一個不具有 DOM 元素的組件,它僅有 Vue 實例的方法,因此非常輕便。
    1
    2
    3
    4
    // eventBus.js

    import Vue from "vue";
    export const EventBus = new Vue();

掛載 EventBus

  1. 如果使用第一種初始化,是直接在 Vue 的原型下掛載 EventBus,所以在初始化 EventBus 時就同步完成全域掛載了。

    1
    Vue.prototype.$EventBus = new Vue();
  2. 若使用第二種方法初始化,獨立宣告並創建出 EventBus 之後,接下來就要在欲使用它的地方掛載它,分為全域級區域掛載。

  • 全域掛載:將 eventBus 加入 root實例中的 data 中,讓每個子組件都能夠使用。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //main.js
    import Vue from "vue";
    import eventBus from "./eventBus.js";

    export default new Vue({
    store, //use store
    i18n,
    el: '#app',
    data: {
    EventBus:eventBus,
    },
    methods: {},
    render: (h) => h(app),
    });
  • 區域掛載:也可以在欲使用的組件中個別引入 eventBus
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//xxx.vue
<template>
// ...略
</template>
<script>
import {EventBus} from ./eventBus.js;
export default{
name:"testComponent",
data(){
return{
// ...略
}
}
}
</script>
<style>
// ...略
</style>

發送事件

  • 全域 EventBuse:如果是全域掛載的 EventBus 可以在任意組件中,利用 $EventBus 取得 EventBus,再呼叫 $emit 送出事件並傳遞資料。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //xxx.vue

    <script>
    // ...略
    methods:{
    testFunc(){
    this.$EventBus.$emit("testEvent",{
    title:"Test Event Title",
    message:"This is test event."
    });
    }
    }
    // ...略
    </script>
  • 區域 EventBuse:因為非全域,所以僅在欲使用的組件中去引入,並使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//xxx.vue

<script>
import {EventBus} from ./eventBus.js;
export default{
name:"testComponent",
data(){
return{
title:"Test Event Title",
message:"This is test event.",
}
},
methods:{
testFunc(){
EventBus.$emit("testEvent",{
title:this.title,
message:this.message
});
}
}
}
</script>

接收事件

  • 全域 EventBuse:可以在任一組件中利用 $on 去監聽,只要監聽事件被觸發,都可以接收到訊息。如果想移除事件的監聽,則使用 $off 不需要添加其他任何參數。

    記得在組件銷毀之前(beforeDestroy),清除監聽事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//xxx.vue

<script>
// ...略
export default{
name:"xxx",
created(){
this.$EventBus.$on("testFunc",event=>{
console.log("Title:"+event.title);
console.log("Msg:"+event.message);
})
},
beforeDestroy(){
this.$EventBus.$off("testFunc");
}
}
// ...略
</script>
  • 區域 EventBuse:在欲使用的組件中去引入 EventBuse,一樣使用 $on 去監聽、使用 $off移除事件監聽
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//xxx.vue

<script>
import {EventBus} from ./eventBus.js;
export default{
name:"testComponent",
data(){
return{
title:"Test Event Title",
message:"This is test event.",
}
},
mounted:{
EventBus.$on("testFunc",{title,message}=>{
console.log("Title:"+title);
console.log("Msg:"+message);
})
},
beforeDestroy:{
EventBus.$off("testFunc");
}
}
</script>

總結

EventBus 身為一個全域的實例,所有的組件都透過 $emit 方法向 EventBus 傳送、更新資料;而其他所有的組件,也同步使用監聽事件 $on 偵測它有沒有發生變化,一但資料數據發生變化,就會獲取 event 其中的資料數據

ES6-其他好用的特色

解構

解構陣列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 原始寫法
const point = [1,2,3];
const x = point[0];
const y = point[1];
const z = point[2];

// ES6作法
const point = [1,2,3]
const [x,y,z] = point;

//只取第一個
const [x] = point;

//只取第二個,用「,」捨去前面的
const[,y] = point

//取第一個跟其他的,用「...」代表
const[x,...others] = point;//others 代表一個陣列[2,3]

//給予預設值,當長度不夠時,會自動補植
const[x,y,z,w = 0] = point;

解構物件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//原始寫法
const point ={ x:1, y:2, z:3 };
const x = point.x;
const y = point.y;
const z = point.z;

//ES6寫法
const { x, y, z} = point;

//只取一個值
const { x } = point;

//只取x跟其他的
const { x, ...others} = point//other代表{y:2, z:3}

//給予預設值,如果原本point裡面沒有值,就會給它預設值
const { x=0, w=0 } = point;//x還是1, w是0

//重新命名
const { x:px=0 } = point;//px是1,可是x會是undefined

解構物件用於函式

1
2
3
4
5
6
7
8
9
10
// 原始寫法
const point ={ x:1, y:2, z:3 };
const draw = point=>{
console.log(point);
}

//解構寫法
const draw = ({x,y,z})=>{
draw(point);//印出1 2 3
}

字串模板

1
2
3
4
5
6
7
8
// 原始寫法
const msg = "I am "+ age +" years old";

//字串模板寫法,支援多行
const msg = `
I am ${age *2} years old.
I have ${brother} brothers and ${sister} sisters.
`;

async/await

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//原始寫法
callSomeApi(function(result){
console.log(result);
})

//promise
callSomeApi().then(result=>{
console.log(result)
})

//async/await寫法
//要使用await,外面一定要包一個async function
//async被還是是使用promise,所以await後面呼叫的函式還是要回傳一個promise
const start = async() =>{
const result = await callSomeApi()
}
start();

ES6-物件導向class

一個class裡面會有兩個基本東西,「屬性」跟「方法」

  • 屬性(property):屬於物件的變數
  • 方法(method):屬於物件的函式

在 ES6 的 class 語法中,只能定義方法,因此必須使用建構子來定義屬性

  • 建構子(Constructor):是一個特別的方法,在「建構實體」時,會被執行的程式內容

在下方 Dog 案例中:

  • age 是屬性
  • bark 是方法
  • Dog 類繼承 Animal 的屬性跟方法(Dog 的父類別是 Animal)
  • new Dog() 來建構實體時,會執行 constructor()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Dog extends Animal{//Animal
    constructor(){
    this.age = 0;
    }
    bark(){
    console.log('woof');
    }
    }
    const spot = new Dog();//當new Dog()時,會執行建構子

使用babel class-properties plugin

  • 使用該 plugin 時,則可以「不需要」透過建構子來定義屬性
  • 且可以使用箭頭函式定義方法,箭頭函式則裡面的 this 等於一開始實體new出來時綁定的物件
    1
    2
    3
    4
    5
    6
    7
    8
    class Dog extends Animal{
    age = 0;
    bark =()=>{
    console.log("bark")
    console.lg(this);//this
    }
    }
    const spot = new Dog();

補:在React中,會使用ES6的class語法,來定義組件(component)

ES6-import與export

使用背景:過去經常會不小心讓一個 main.js 程式碼長達千、萬行
優化目的:可以模組化管理程式碼,方便協作跟測試

解決方式:為了達到模組化管理,ES6 語法出現import 、 export,可以先將特定功能拆分到另外一個檔案中,之後再 import 到主要的檔案裡作使用

案例:假設現在要定義一個數學計算功能
可以先將這些數學計算包成一個物件,所以先新增一個 math.js 檔案,在其中地義好並且export出一個物件

1
2
3
4
5
6
7
8
//math.js

const math = {
double: x => x * 2,
square: x => x * x,
area: (w,h) => w * h
};
export default math;

接著,在主要檔案 main.js 檔案中,import近這個物件,並做執行使用

  • 被import的物件可以隨意命名
  • 而依據實際狀況,須放上檔案正確的參考路徑
  • 副檔名’.js’可以省略
    1
    2
    3
    //main.js
    import m from "./math.js";
    console.log(m.square(2));//印出4

Named export
在 export 時,給予一個名字

1
2
3
//math.js
export const PI = 3.1415;
export const ROOT_2 = 1.414;

在引入時,用大括號包起 named 物件

1
2
3
//main.js
import {PI,ROOT_2} from"./math";//被import的物件命名必須跟export時的名稱一樣
console.log(PI,ROOT_2)//3.1415 1.414

default 跟 named 可以混用

1
2
3
4
5
6
7
// math.js
const math = {
...略
}
export const PI = 3.1415;
export const ROOT_2 = 1.414;
export default math;

1
2
//main.js
import m,{PI,ROOT_2} from "./math.js"

重新命名named export
在 import named 物件時,可以用 as 來重新命名

1
2
3
//main.js
import m,{PI as P,ROOT_2 as R} from "./math.js";
console.log(P,R)

ES6-箭頭函示

宣告函式更簡化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//宣告函式
function double(n){
return n*2;
}

var double = function(n){
return n*2;
}

//箭頭函示
const double = (n)=>{
return n*2;
}

//簡化箭頭函示
//如果只有一個參數,可以省略括號
const double = n =>{
return n*2
}

//簡化箭頭函示
//如果函式本體只有一行,且return一個值,可以省略大括號、return
const double = n => n*2

函式裡的this

this 指的是函式執行的情境(context),因此 this 代表什麼?取決於如何執行函式

第一種:直接執行函式,this代表window

1
2
3
4
function jump(){
console.log(this);//this是window
}
jump();

第二種:函式作為物件的方法來執行,其中的this代表物件本身

1
2
3
4
5
6
7
function jump(){
console.log(this);//this是a
}

const a = {};
a.jump = jump;
a.jump();

第三種:函式作為DOM的偵聽函式,其中的this代表DOM元件

1
<button id="btn">btn</button>

1
2
3
4
5
function jump(){
console.log(this);//this是 <button id="btn">btn</button>
}

btn.addEventListener("click",jump);

箭頭函式裡的this

箭頭沒有自己的情境(context),它內部的context就等於它外面的context,函式在哪裡被宣告函式,它裡面的this就代表被宣告時的環境。

如果,把函式改成箭頭函式,因為宣告函式的環境是global,因此箭頭函式中的this就代表global環境的物件,也就是window,不論用哪種方式執行函式一樣

1
2
3
4
const jump =()=>{
console.log(this)//this是window
}
jump();

1
2
3
4
5
6
7
const jump =()=>{
console.log(this)//this是window
}

const a = {};
a.jump = jump;
a,jump();

ES6-取代var的let跟const

let 跟 const

let可以重新賦值,const不能重新賦值(re-assign)

1
2
let age = 20;
age = 22;

1
2
3
4
5
6
7
const gender = "F";
gender = "M";//[錯誤]const不能重新賦值,會出錯

const point ={x:50,y:100}
body.weight = 60;//可以修改body物件

point ={x:200,y:100}//[錯誤]不可以把新的物件重新賦值給const,會出錯

先了解scope

了解var之前,要先了解Javascript的scope跟block

  • Scope(作用域)有兩種,function scopeglobal scope,只要不是在函式裡面,就是global scope
  • scope會由外往內繼承,反過來也就是說,內部會繼承外部的作用域

下方案例中,x屬於global scope,而f函式內屬於function scope,雖然funciton scope中沒有定義x,可是function scope中會繼承global scope,因此是可以印出x

1
2
3
4
5
6
7
var x =1;
function f(){
console.log(x);//x
}

f();
//x

兩個作用域都有分別定義x,因為先執行f函式,所以先進去f中執行印出2,接著回到global scope中執行印出1

1
2
3
4
5
6
7
8
9
var x = 1;
function f(){
var x = 2;
console.log(x);
}
f();
console.log(x);
//2
//1

什麼是block

用大括號包起來的範圍,就是block(除非物件的{})

  • 在程式碼中,可以隨時宣告block
  • block裡面有程式碼
  • block跟scope一樣是由外向內繼承
    1
    2
    3
    4
    5
    6
    7
    {
    const x = 1;
    const y = x+1;
    function f(){
    console.log(y);
    }
    }

var 跟 let/const 的作用範圍

  • var的作用範圍是 scope
  • let/const 的作用範圍是 block

因為var的作用範圍是scope,而非block,所以即使在block內/外都宣吿x,可是var會穿越block,導致block裡面跟外面的x會合而為一,兩次印出的結果都是2

1
2
3
4
5
6
7
8
var x = 1;
{
var x = 2;
console.log(x)
}
console.log(x);
//2
//2

同樣的例子改為let做宣告,因為let的作用範圍是block,所以程式碼由上而下執行到block中會先印出2,接著結束block之後再印出外面的1

1
2
3
4
5
6
7
8
let x = 1;
{
let x = 2;
console.log(x);
}
console.log(x);
//2
//1

在ES6出現let/const之後,let/const可以完全取代var,所以鮮少再使用var了

© 2020 Leah's Blog All Rights Reserved. 本站访客数人次 本站总访问量
Theme by hiero