React 是一個 View 框架,主要的用途有幾個:

  • 將UI物件化
  • 運用 props 來傳輸數據
  • 依照 state 變化來調整 view
  • 使用虛擬 DOM 提升效能

在開始之前,請預先安裝 webpack + babel + react依賴環境

接下來,會一步步來說明:

【React element 及 React Dom】

React DOM負責管理要輸出到指定目標的內容 例如,先產生一個div

<div id="root"></div>

接下來,建立一個react element內容, 在透過 ReactDOM.render() 輸出到 root

import React from 'react';
import ReactDOM from 'react-dom';
let react_element = <h1>Hello World!</h1>;
ReactDOM.render(react_element, document.getElementById('root'));

immutable

React element只要經過ReactDOM.render()輸出後,就具有**不可變動(immutable)**的特性, 因此輸出的主要架構(包括子元件及屬性)將維持固定, 如果需要更新內容,也只會額外處理避免操作整個DOM,盡可能做到最小幅度異動。

例如,官網提供的時間範例語法

import React from 'react';
import ReactDOM from 'react-dom';

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(
    element,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

實際執行,會發現每秒取得一次時間後,都只會變更指定的位置,而不是整個react element區塊

這是因為,ReactDOM 會比較前後差異,並且只針對有差異的地方進行最小幅度修改。

建立 Components 的三種方式及export用法

(1) function

Components 可以將UI分離出來,並且可以重複使用 功能跟JavaScript function一樣 Components可以傳入props參數,並且返回 React elements

function Title_elem(obj){
  return <h1>{obj.title}</h1>;
}

let element = <Title_elem title="Hello World" />;

ReactDOM.render(element, document.getElementById('root'));

(2) 物件

也可以使用物件的方式建立

/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';

const A = {
  a: function (props){
    return <div>this is a</div>;
  },
  b: function (props){
    return <div>this is b</div>;
  },
  c: function (props){
    return <div>this is c</div>;
  }
}

ReactDOM.render(
      <div>
        <A.a />
        <A.b />
        <A.c />
      </div>, document.getElementById('root'));

(3) Class 方式 + Extends React.component

通常會使用 ES6 的 class 來管理 Components Class 只要擴充 React.Component ,就可以使用 React Component功能:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

export imoprt Component

myComonents.js

export myComponent;

App.js

import myCommponetn from './myComponent'

接下來,要介紹一下JSX

JSX

JSX比較接近於javascript而不是HTML,使用的是駝峰式(camelCase)命名方式。 JSX架構與HTML相當類似,因此,只要寫你熟悉的HTML就能開始建構JSX內容, 並且,透過Babel就能將JSX風格轉換成React語句, 這裡我們會先跳過 react 語句,直接以JSX為重點。

並且在rendering之前,ReactDOM 進行格式化,會將 JSX 裡的數據資料轉換為字符,因此,正常情況使用都不必擔心XSS攻擊的風險。

例如,在這案例的JSX {} 可以傳入外來的變數值,因此可能會有夾帶XXS攻擊的字串,

return <div>this is c {'<script>console.log("攻擊");</script>'}</div>;

這些內容會被判定為 HTML-unescaped ,而直接處理成字串格式

接下來,我們會一邊學習 react架構,一邊講解 JSX 用法:

基本架構

import React from 'react';
import ReactDOM from 'react-dom';

ReactDOM.render(<h1>Hello World</h1>, document.getElementById('root'));

崁入變數

import React from 'react';
import ReactDOM from 'react-dom';

let name = 'Adam';
ReactDOM.render(<h1>Hello World! {name}</h1>, document.getElementById('root'));

崁入函式

import React from 'react';
import ReactDOM from 'react-dom';

let userfn = (user)=>{
  if(user.age>=18){
    return user.firstname+' '+user.lastname
  }else{
    return 'Your should not be here! age:'+user.age;
  }
};
let user = {firstname: 'Adam', lastname: 'OuYang', age: 31};

ReactDOM.render(<h1>Hello {userfn(user)}~</h1>, document.getElementById('root'));

HTML屬性

屬性值

用變數賦予屬性值,且不必使用引號包裹

import React from 'react';
import ReactDOM from 'react-dom';

let imagesrc = "https://i.pinimg.com/originals/12/e5/cb/12e5cbbb63024460755560bb7e03f0a4.png";
ReactDOM.render(<img src={imagesrc}></img>, document.getElementById('root'));

class -> className

JSX使用駝峰式(camelCase)來描述原本的HTML屬性 在HTML標籤中,我們常會用 class 等屬性, JSX要修改成 className

ReactDOM.render(<h1 className="main_title">Hello</h1>, document.getElementById('root'));

for -> htmlFor

使用 htmlFor 來對應 for功能

import React from 'react';
import ReactDOM from 'react-dom';

function Hello(props){
  return <div className='hello'>
            <label htmlFor="a">
                Hello
            </label>
            <input id='a' type='text' value='hi' />
          </div>
}

ReactDOM.render(<Hello />, document.getElementById('root'));

Boolean 操作屬性

HTML標籤有些屬性可以用 Boolean 來操作 預設為 false

<input id='a' type='text' disabled={false}  value='hi' />

搭配Components

function Img_elem(obj){
  return <img src={obj.src} />;
}

const imagesrc = "https://i.pinimg.com/originals/12/e5/cb/12e5cbbb63024460755560bb7e03f0a4.png";

ReactDOM.render(<Img_elem src={imagesrc} />, document.getElementById('root'));

參考官網提供的範例

function formatDate(date) {
  return date.toLocaleDateString();
}

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
             src={props.author.avatarUrl}
             alt={props.author.name} />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'http://placekitten.com/g/64/64'
  }
};
ReactDOM.render(
  <Comment
    date={comment.date}
    text={comment.text}
    author={comment.author} />,
  document.getElementById('root')
);

ES6 Class

將 components 獨立出來, 這次我們透過ES6 class 建立一個 component 透過 default 可以將此類別認定為預設, 在這裡面,透過 constructor 來設定props,並且要透過 super()屬性才能連結回 React.Component,修改React.Component的constructor屬性。 內容則套用 constructor定義的 state /client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date().toLocaleTimeString()};
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date}.</h2>
      </div>
    );
  }
}

這裡是 ./client/index.js

/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';


ReactDOM.render(<App />, document.getElementById('root'));

Component Mount and unMount

https://reactjs.org/docs/state-and-lifecycle.html React Component 提供元件加載及卸載功能

componentDidMount() - 當元件輸出至DOM之後,就會執行 componentWillUnmount - 當元件從DOM移除,就會執行 例如,這裡實作一個時間顯示器

範例: ./client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

  tick() {
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

關於 constructor setState()

上方案例中,會看到一個比較特殊的狀況 在 constructor 我們設定了 this.state = {date:初始值} 在計時器呼叫的函式要變更初始值時,要使用 this.setState({data: 變更值})

要留意的是,不能使用這個寫法,會造成無法 re-render

this.state.date = new Date();

而是透過this.setState()修改constructor 的state值,才能成功re-render

    this.setState({
      date: new Date()
    });

使用 setState()的原因是為了效能, 因為React會將多處的setState()呼叫都統合起來,並一次更新

事件

https://reactjs.org/docs/handling-events.html 在說明事件相關範例時,都是使用 index.js 內容, 後續將專注在App.js 解說事件的用法 client/index.js

/* ./client/index.js*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';

ReactDOM.render(<App />, document.getElementById('root'));

onClick

onClick 可以用來觸發點擊事件,在物件中, 如果要呼叫內部方法,要以 this.方法名稱 來呼叫 例如,範例中的 A() B() 方法,在render都必須要用 this.A, this.B 來呼叫

另外,如果呼叫方法之後,還需要繼續呼叫其他方法等callback行為, 則必須要在 constructor 裡面宣告this,讓render的元件可以callback 例如,在範例中,A()就有在 constructor 實作宣告this,因此可以繼續在A來回呼內部 Msg 方法

或者,可以在 render 使用 arrows 來達到 callback,但是這樣的作用可能會有re-rending的問題,因此還是建議用上面A的方法

如果是直接在render寫function,就不必再額外加 this 例如,範例中的函式 Fn,但Fn同樣無法callback

直接從範例中來了解上面內容:

client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    // This binding is necessary to make `this` work in the callback
    this.A = this.A.bind(this);
  }

  A(){
    console.log('The link A was clicked.');
    console.log(this);
    this.Msg();
  }

  B(){
    console.log('The link B was clicked.');
    console.log(this);//undefined
    this.Msg();//TypeError
  }

  Msg(){
    console.log('Yes! I can callback');
  }

  render() {
    function Fn(e) {
      e.preventDefault();
      console.log('The link C was clicked.');
      console.log(this);//undefined
      this.Msg();//TypeError
    }
    return (
      <div>
        <h1>Hello, world!</h1>
        <a href="#" onClick={this.A}>Call A</a><br/>
        <a href="#" onClick={this.B}>Call B</a><br/>
        <a href="#" onClick={(e) => this.B(e)}>Call B</a>(This syntax ensures `this` is bound within B)<br/>
        <a href="#" onClick={Fn}>Call Fn</a><br/>
      </div>
    );
  }
}

refs

React 支援一個特殊的屬性 Refs,可以綁定到任何被 render的組件上面,確保能在任何時間都能取得最新的值或實作。 https://reactjs.org/docs/refs-and-the-dom.html

var MyComponent = React.createClass({
  handleClick: function() {
    this.refs.myInput.focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myInput" />
        <input
          type="button"
          value="點擊獲得焦點"
          onClick={this.handleClick}
        />
      </div>
    );
  }
});
 
ReactDOM.render(
  <MyComponent />,
  document.getElementById('example')
);

傳入事件參數

這兩種方式,都可以用來傳入參數

<a href="#" onClick={this.A.bind(this,'1','2')}>Call A</a><br/>
<a href="#" onClick={(e) => this.A('1','2', e)}>Call A</a><br/>

將前面的例子修改成可以傳參數: client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    // This binding is necessary to make `this` work in the callback
    this.A = this.A.bind(this);
  }

  A(id,id2,e){
    console.log(e);
    console.log('The link A was clicked. id is :'+id+', id is:'+id2);
    console.log(this);
    this.Msg();
  }

  B(id,id2, e){
    console.log(e);
    console.log('The link B was clicked. id is :'+id+', id is:'+id2);
    console.log(this);
    this.Msg();
  }

  Msg(){
    console.log('Yes! I can callback');
  }

  render() {
    function Fn(id, id2, e) {
      console.log(e);
      console.log('The link C was clicked. id is:'+id+', id is:'+id2);
      // console.log(this);//undefined
      // this.Msg();//TypeError
    }
    return (
      <div>
        <h1>Hello, world!</h1>
        <a href="#" onClick={this.A.bind(this,'1','2')}>Call A</a><br/>
        <a href="#" onClick={(e) => this.B('1','2', e)}>Call B</a>(This syntax ensures `this` is bound within B)<br/>
        <a href="#" onClick={Fn.bind(this,'1','2')}>Call Fn</a><br/>
        <a href="#" onClick={(e) => Fn('1','2', e)}>Call Fn</a><br/>
      </div>
    );
  }
}

Components Rendering

Components displays Components 及事件參數

在React,可以封裝許多 Components 而 Components 也可以使用其他 Components 例如,這裡舉例一個依照使用者登入狀況,先透過 Greeting Components 決定要返回甚麼介面 當使用者登入,Greeting Components 會返回 UserGreeting Components,讓他被 rending 若未登入,則會返回 GuestGreeting Components

關於 展開運算子 Spread Operator …

在這例子中,我們還使用到了一個ES6 展開運算子(Spread Operator) ...

var datas = {name:'adam', city:'taipei', country:'taiwan'}
ReactDOM.render(
  <div>
    <A  {...datas}/>
  </div>
  ,document.getElementById('root')
);

function A(props){
  return (
    <span>
      I'm {props.name}, I live in {props.city},{props.country}
    </span>
  );
}

CVT2HUGO: ,可以將 props 物件一次pass給Component。 同理,也可以在多個components之間,以spread attributes傳遞props

var datas = {name:'adam', city:'taipei', country:'taiwan'}
ReactDOM.render(
  <div>
    <A  {...datas}/>
  </div>
  ,document.getElementById('root')
);

function A(props){
  return (
    <span>
      I'm {props.name}, <B {...props} />
    </span>
  );
}

function B(props){
  return (
      <span>I live in {props.city}, <C {...props} /></span>
  );
}

function C(props){
  return (
      <span>{props.country}</span>
  );
}

範例: client/index.js

/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';


function UserGreeting(props) {
  return <h1>{props.user} Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

function Greeting(props){
    if(props.isLoggedIn){
      //透過...將props參數完整傳輸到 UserGreeting 且返回 Components
      return <UserGreeting {...props} />
    }else{
      //返回GuestGreeting Component
      return <GuestGreeting />
    }
}

ReactDOM.render(<Greeting isLoggedIn={true} user='Admin'/>, document.getElementById('root'));

在類別進行 Components displays Components

這裡,我們將上述的內容搬到App.js, 在這裡,建立一個登入按鈕及訊息,並且要能根據登入狀況來切換按鈕文字及訊息

client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {

    function UserGreeting(props) {
      return <h1>Welcome back!</h1>;
    }

    function GuestGreeting(props) {
      return <h1>Please sign up.</h1>;
    }

    function Greeting(props) {
      const isLoggedIn = props.isLoggedIn;
      if (isLoggedIn) {
        return <UserGreeting />;
      }
      return <GuestGreeting />;
    }

    function LoginButton(props) {
      //這裡的 onClick={props.onClick} 會呼叫 A處的 this.handleLogoutClick
      return (
        <button onClick={props.onClick}>
          Login
        </button>
      );
    }

    function LogoutButton(props) {
      //這裡的 onClick={props.onClick} 會呼叫 B處的 this.handleLogoutClick
      return (
        <button onClick={props.onClick}>
          Logout
        </button>
      );
    }

    const isLoggedIn = this.state.isLoggedIn;

    let button = null;
    if (isLoggedIn) {
      //A處
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      //B處
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

&& Operator

在 component 設計中,可以加入判斷式來決定要 rendering 的內容, 這裡說明 && 用法, 當我們判斷是否有訊息,如果訊息長度大於1且(&&)包含 element,就返回此 element 當然,element已經寫好,所以會是 true 因此這個component是否返回element 就要取決於訊息長度是否大於1

client/index.js

/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 &&
        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
      
    </div>
  );
}

const messages = ['React', 'Re: React', 'Re:Re: React'];

ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));

? true : false

在component透過 ```?

client/index.js

CVT2HUGO: true : false``` 來判斷要返回的 element寫法
/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';

function Mailbox(props) {
  const unreadMessages = props.unreadMessages;
  return (
    <div>
      <h1>Hello!</h1>
      {unreadMessages.length > 0 ?(
          <h2>
            You have {unreadMessages.length} unread messages.
          </h2>
        ) : (
          <h2>
            You have no messages.
          </h2>
        )
      }

    </div>
  );
}

//const messages = ['React', 'Re: React', 'Re:Re: React'];
const messages = [];

ReactDOM.render(<Mailbox unreadMessages={messages} />, document.getElementById('root'));

資料列 Lists 與 keys

在React資料列的處理有點特別 當我們輸出重複相同的姊妹群(Siblings)list時,React會提出警告,

Warning: Each child in an array or iterator should have a unique "key" prop.

keys

意思是,必須賦予該map渲染的每一個Siblings(姊妹群)元素唯一keys,讓React再生成元素之後,可以辨識誰是誰, 之後就能辨識出元素是否有新增、刪除、變更

這裡,據一個例子,當我們透過 map 迴圈生成Siblings元素,就要附上 key 這個相關的 li 群裡,此key 必須要有唯一性,但不必具有絕對唯一性。這點我們看完範例後,接續會再討論

這裡的key我們用的是陣列的索引值 client/index.js

/* 
    ./client/index.js
*/
import React from 'react';
import ReactDOM from 'react-dom';

function Hello (props) {
  const numbers = props.num;
  const NumList = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
  const NumList2 = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
  return (
    <div>
      <h1>Hello!</h1>
      <ul>{NumList}</ul>
      <ul>{NumList2}</ul>
    </div>
  );
}

const numbers = [1, 1, 2, 3, 5];

ReactDOM.render(<Hello num={numbers}/>, document.getElementById('root'));

keys 只需在該Siblings具有唯一性

剛剛提過,這個相關的 map 產出的Siblings(姊妹群)元素裡,此key 必須要有唯一性,但不必具有絕對唯一性。 這組key只是React方便於辨識該Siblings元素,所以,其他地方仍可以使用 例如,我們將陣列分別儲存在 numbers,numbers2,並且各自將map結果存在NumList Siblings, NumList2 Siblings 雖然 NumList, NumList2 的 key 有重複,但是不會影響

function Hello (props) {
  const numbers = props.num;
  const numbers2 = props.num;
  const NumList = numbers.map((v,k)=><li key={k.toString()}>{v}</li>)
  const NumList2 = numbers2.map((v,k)=><li key={k.toString()}>{v}</li>)
  return (
    <div>
      <h1>Hello!</h1>
      <ul>{NumList}</ul>
      <ul>{NumList2}</ul>
    </div>
  );
}

表單控制

在 React,JSX事件、屬性都要遵循駝峰式寫法, 因此,表單送出事件,輸入欄位變更事件等…都要調整寫法

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }

event.target

React 在 rendering 元素之後,如果事件要傳回元素本身,就要透過 event.target client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {val: ''};
  }

  handleChange(event){
    this.setState({
      val:event.target.value
    });
    console.log(event.target.value);
  }

  render() {

    return (
      <div>
          <input type="text" onChange={this.handleChange} /><br/>
          <h2>{this.state.val}</h2>
      </div>
    );
  }
}

取得input value

在類別裡,要取得 render input 之後輸入的值,一樣使用 event.target.value client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {value: ''};

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }
  
  render() {

    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

Textarea

在React,textarea要以value的屬性來賦予值,例如:

<textarea value='hello world' />

範例

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleTxtChange = this.handleTxtChange.bind(this);
    this.state = {val: '', txtVal: ''};
  }
  handleTxtChange(event){
    this.setState({
      txtVal:event.target.value
    });
    console.log(event.target.value);
  }

  render() {

    return (
      <div>
          <textarea value={this.state.txtVal} onChange={this.handleTxtChange} /><br/>
          <h2>{this.state.txtVal}</h2><br/>
      </div>
    );
  }
}

select

在 React,select是透過 value 屬性來控制 option

<select value='b' >
	<option value="a">A option</option>
	<option value="b">B option</option>
</select>

如果 select 是多選(multiple),則只要將陣列值提供給value

<select multiple={true} value={['b', 'c']}>
	<option value="a">A option</option>
	<option value="b">B option</option>
	<option value="c">C option</option>
</select>

範例

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleSelChange = this.handleSelChange.bind(this);
    this.state = {selVal: 'c'};
  }
  handleSelChange(event){
    this.setState({
      selVal:event.target.value
    });
    console.log(event.target.value);
  }

  render() {

    return (
      <div>
          <select value={this.state.selVal} onChange={this.handleSelChange}>
            <option value="a">A option</option>
            <option value="b">B option</option>
            <option value="c">C option</option>
            <option value="other">Other option</option>
          </select>
          <br/>
          <h2>{this.state.selVal}</h2><br/>
      </div>
    );
  }
}

多 Input 處理方式

在表單中,會使用到多個輸入欄位,或者勾選欄位等… 會透過 name 屬性來幫不同input命名,以及透過 type 來定義欄位屬性

  Last name:
  <input 
      name="lastname" 
      type="input" 
      value={this.state.lastname} 
      onChange={this.handleSelChange} /><br/>

  sexual:
  <input 
      name="sexual" 
      type="checkbox" 
      checked={this.state.sexual} 
      onChange={this.handleSelChange} /><br/>

當同時存在多個 input,其中type 包括 text, checkbox 底下這範例會說明如何取得各個值,及輸出 以及透過 this.setState({[name]:value});方式來控制

client/components/App.jsx

CVT2HUGO: state 對應值
/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleSelChange = this.handleSelChange.bind(this);
    this.state = {
      firstname: '',
      lastname: '',
      sexual: true
    };
  }
  handleSelChange(event){
    const target = event.target;
    const name = target.name;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    this.setState({
      [name]:value
    });
  }

  render() {

    return (
      <div>

          First name:
          <input 
              name="firstname" 
              type="input" 
              value={this.state.firstname} 
              onChange={this.handleSelChange} /><br/>

          Last name:
          <input 
              name="lastname" 
              type="input" 
              value={this.state.lastname} 
              onChange={this.handleSelChange} /><br/>

          sexual:
          <input 
              name="sexual" 
              type="checkbox" 
              checked={this.state.sexual} 
              onChange={this.handleSelChange} /><br/>

          <hr />

          <h2>First name:{this.state.firstname}</h2><br/>
          <h2>Last name:{this.state.lastname}</h2><br/>
          <h2>sexual:{this.state.sexual?'Male':'Female'}</h2><br/>
      </div>
    );
  }
}

提升狀態 (Lifting State Up)

在 React 有一個提升狀態(Lifting State Up)的觀念

也就是,相關聯的component之間要互相傳遞數據及狀態, 統一將這些數據狀態提升到最接近的父層 components, 從這個父層來管理底下各components所需的數據、方法等狀態,

這裡舉例,我們想要建立兩個輸入欄位,並且最後要統計總和

先建立主要的父層 App 物件,接續建立 B 物件(文字輸入框)、C物件(文字輸入框)、Sum物件(總和) 這時透過 App 來負責建構主要的數據及方法,並且在render B、C、Sum同時, 會將這些方法及值各別帶入, 當B、C輸入文字變更狀態,回傳通知App,App會在統整資訊再傳給Sum來顯示總和

client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleBChange = this.handleBChange.bind(this);
    this.handleCChange = this.handleCChange.bind(this);
    this.state = {bdata: '',cdata: ''};
  }

  handleBChange(e) {
    console.log(e);
    this.setState({bdata: e});
  }

  handleCChange(e) {
    console.log(e);
    this.setState({cdata: e});
  }

  render() {
    return (
      <div>
        <B bdata={this.state.bdata} onBChange={this.handleBChange} />
        <C cdata={this.state.cdata} onCChange={this.handleCChange} />
        <Sum bdata={this.state.bdata} cdata={this.state.cdata} />
      </div>
    );
  }
}

class B extends React.Component {
  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(e) {
    this.props.onBChange(e.target.value);
  }

  render() {
    return <p>B <input name="b" onChange={this.onChange} /></p>
  }
}

class C extends React.Component {
  constructor(props) {
    super(props);
    this.onChange = this.onChange.bind(this);
  }

  onChange(e) {
    this.props.onCChange(e.target.value);
  }

  render() {
    return <p>C <input name="c" onChange={this.onChange} /></p>
  }
}

class Sum extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return (
      <div>
        <hr />
        {this.props.bdata && 
          <p>B值為 {this.props.bdata}</p>
        }
        {this.props.cdata && 
          <p>C值為 {this.props.cdata}</p>
        }
        {this.props.bdata && this.props.cdata &&
          <p>B+C={parseInt(this.props.bdata)+parseInt(this.props.cdata)}</p>
        }
      </div>
    );
  }
}

React 提倡元件,而不是繼承

在 React 提供了 components ,讓我們可以很快的透過組件來設計 但是對於剛接觸React的人,可能還會使用繼承(inheritance)的方式,

在Facebook,他們建立了上千個 components 當中,還沒有出現需要使用繼承方式的案例, 透過元件以及props,讓他們能更靈活的客製各種UI及操作行為。

在這裡就是要說明,為什麼要用 components,以及component如何解決inheritance問題

props.children 取得元素物件鑲嵌內容

在React 有一個**特殊案例 (“special cases”)**概念 一個特殊案例元件,可以render出多個 components,並且傳送 props 給這些 components

以接下來的範例來說,對於其他相關聯的 components 而言, App components 就是一個 “special cases” ,會透過props傳送訊息給A,且在render A 時,夾帶了一些元素 相關的components可以透過 props來取得參數, 以及透過props.children來取得鑲嵌在“special cases” render 該物件內的元素。

下面解釋可能會比較清楚這段的用意" 這裡直接用範例來說明 props.children 的用法, 我們建立一個 App component class,並且準備 return 一個 A元素物件,且A元素物件裡包含了一些元素,

export default class App extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    //傳送一個訊息給A元件
    return (
      <A specialCasesMsg='hello A~ This is a message from special cases'>---A元件------
      
      	----這裡面就是render A時所夾帶的元素----
        <h2>Child element</h2>
        <p>Chldren element will pass directly into A components</p>
      
      </A>
    );
  }
}

A components 只要呼叫 props.children 就可以取得該元素

function A(props) {
  return (
    <div>
      <h1>A components</h1>
      <p>{props.specialCasesMsg}</p>
      {props.children}
    </div>
  )
}

client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    //傳送一個訊息給A元件
    return (
      <A specialCasesMsg='hello A~ This is a message from special cases'>
      	----這裡面就是render A時所夾帶的元素----
        <h2>Child element</h2>
        <p>Chldren element will pass directly into A components</p>
      </A>
    );
  }
}

function A(props) {
  return (
    <div>
      <h1>A components</h1>
      <p>{props.specialCasesMsg}</p>
      {props.children}
    </div>
  )
}

porps 傳遞元素物件

由“special cases”來主導,並且讓底下元素物件可以透過 props來互相引用,

這裡舉例,App components可以把新建立的 B, C components,透過 props 直接傳遞給 A components 因此,就能在A裡面用 props 的方式來使用 B, C

當然,B、C也可以用 Class 的方式來建構,功能都不會變~

範例 client/components/App.jsx

/*
    ./client/components/App.jsx
*/
import React from 'react';

export default class App extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return (
      <A specialCasesMsg='hello A~ This is a message from special cases' getb={<B/>}  getc={<C />}>
        <h2>Child element</h2>
        <p>Chldren element will pass directly into A components</p>
      </A>
    );
  }
}

function A(props) {
  return (
    <div>
      <h1>A components</h1>
      <p>{props.specialCasesMsg}</p>
      {props.children}
      {props.getb}
      {props.getc}
    </div>
  )
}

function B(props){ return ( <p>This is B</p> ) }

function C(props){ return ( <p>This is C</p> ) }

最後,

對於一些非UI功能的操作,建議可以獨立成一個 Module, 需要的components只需要導入這個 Module,而不需要特別用 extends 繼承

錯誤處理

< IE11 時的處理方式

requestAnimationFrame 環境變數支援

Warning: React depends on requestAnimationFrame. Make sure that you load a polyfill in older browsers. http://fb.me/react-polyfills https://reactjs.org/docs/javascript-environment-requirements.html https://dev.to/letsbsocial1/requestanimationframe--polyfill-in-react-16-2ce

MAP & SET

yarn add core-js

在主要index.js載入

import 'core-js/es6/map';
import 'core-js/es6/set';
import React from 'react';
import ReactDOM from 'react-dom';
yarn add raf
import 'raf/polyfill';

export default 在IE8的問題

(目前尚未解決) 或許react-scripts-ie8 可以試試看 http://blog.404mzk.com/es6ie8.html https://github.com/iineva/react-scripts-ie8/blob/master/package.json