前端寶庫

前端技術分享平台

0%

[筆記]_React

v17.0.2

React 是什麼?

React 是一個陳述式、高效且具有彈性的 JavaScript 函式庫,用以建立使用者介面。
它讓你使用小巧而獨立的 component,來建立複雜的 UI。

開發環境

  • 安裝

    若要開發標準的 React 專案,建議使用 create-react-app 來建立新專案。

    npm i -g create-react-app
  • 建立新專案

    create-react-app <專案名稱>

主要概念

  • Hello World

    ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
    );
  • JSX 介紹

    JSX 是一種 JavaScript 的擴充語言,加入了一些 HTML 標籤的語法。
    React 架構在設計上將 HTML 標籤與 JavaScript 控制邏輯合併,以 JSX 來描述 UI 的外觀與運作邏輯,
    打造出 React 的 UI 組件(components),再用這些 UI 組件堆疊出個應用程式。

    • 在 JSX 中嵌入 Expression

      const name = 'Josh Perez';
      const element = <h1>Hello, {name}</h1>;

      ReactDOM.render(
      element,
      document.getElementById('root')
      );
      function formatName(user) {
      return user.firstName+ ' ' + user.lastName;
      }

      const user = {
      firstName: 'Harper',
      lastName: 'Perez'
      };

      const element = (
      <h1>
      Hello, {formatName(user)}!
      </h1>
      );

      ReactDOM.render(
      element,
      document.getElementById('root')
      );
    • JSX 本身也是 Expression

      使用 JSX 作為參數並由 function 中回傳。

      function getGreeting(user) {
      if (user) {
      return <h1>Hello, {formatName(user)}!</h1>;
      }
      return <h1>Hello, Stranger.</h1>;
      }
    • 在 JSX 中指定屬性

      • 可以使用引號將字串設定為屬性:
        const element = <div tabIndex="0"></div>;
      • 也可以在屬性中使用大括號來嵌入一個 JavaScript expression:
        const element = <img src={user.avatarUrl}></img>;

        不要在嵌入 JavaScript expression 作為屬性的時候同時使用引號或是大括號。

        React DOM 使用 camelCase 來命名屬性而不是使用慣有的 HTML 屬性名稱。
        舉例來說:在 JSX 之中,class 變成了 className 而 tabindex 變成了 tabIndex。

      • 在 JSX 中指定 Children
        const element = <img src={user.avatarUrl} />;
        const element = (
        <div>
        <h1>Hello!</h1>
        <h2>Good to see you here.</h2>
        </div>
        );
      • JSX 表示物件
      const element = (
      <h1 className="greeting">
      Hello, World!
      </h1>
      );

      Babel 將 JSX 編譯為呼叫 React.createElement() 的程式。

      const element = React.createElement(
      'h1',
      {className: 'greeting'},
      'Hello, World!'
      );
  • Render Element

    建立 React 應用程式最小的單位是 element。
    與瀏覽器的 DOM element 不同,
    React element 是單純的 object,而且很容易被建立。
    React DOM 負責更新 DOM 來符合 React element。

    • Render Element 到 DOM內

      <div id="root"></div>
      使用 React 建立應用程式時,通常會有一個單一的 root DOM node。
      如果想要整合 React 到現有的應用程式時,可以根據需求獨立出多個 root DOM node。
      如果要 render 一個 React element 到 root DOM node,傳入兩者到 ReactDOM.render()
      const element = <h1>Hello, world</h1>;
      ReactDOM.render(
      element,
      document.getElementById('root')
      );
    • 更新被 Render 的 Element

      React element 是 不可變動性 的。
      一旦你建立一個 element,你不能改變它的 children 或是 attribute。
      更新 UI 唯一的方式是建立一個新的 element,並且將它傳入到 ReactDOM.render()
      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);
      // 從 setInterval() callback 每秒呼叫 ReactDOM.render()。
    • React 只更新必要的 Element

      React DOM 會將 element 和它的 children 與先前的狀態做比較,
      並且只更新必要的 DOM 達到理想的狀態。
      只有內容更改的 node 才會被 React DOM 更新。
  • Components 與 Props

    概念上來說,component 就像是 JavaScript 的 function,
    它接收任意的參數(稱之為「props」)並且回傳描述畫面的 React element。

    • Function Component 與 Class Component

      定義 component 最簡單的方法即是撰寫一個 Javascript function:
      function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
      }
      也可以使用 ES6 Class 來定義 component:
      class Welcome extends React.Component {
      render() {
      return <h1>Hello, {this.props.name}</h1>;
      }
      }
    • Render 一個 Component

      React element 也可以是使用者自定義的 component:
      當 React 看到由使用者定義 component 的 element 時,
      它將 JSX 屬性和 children 作為 single object 傳遞給該 component。
      我們稱這個 object 為「props」。
      function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
      }

      const element = <Welcome name="Sara" />;
      ReactDOM.render(
      element,
      document.getElementById('root')
      );
      React 以 {name: 'Sara'} 作為 props 傳入 Welcome component 並呼叫。
      Welcome component 回傳了 <h1>Hello, Sara</h1> 這個 element 作為返回值。

      Component 的字首須為大寫字母。
      React 會將小寫字母開頭的組件視為原始 DOM 標籤。

    • 組合 Component

      Component 可以在輸出中引用其他 component。
      可以在任何層次中抽象化相同的 component,按鈕、表單、對話框、甚至是整個畫面,
      在 React 應用程式中都將以 component 的方式呈現。
      function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
      }

      function App() {
      return (
      <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
      </div>
      );
      }

      ReactDOM.render(
      <App />,
      document.getElementById('root')
      );
      通常來說,每個 React 應用程式都有一個最高層級的 App component。
      然而,將 React 結合至現存的應用程式中,可能需要使用像 Button 這樣的小型 component,
      並由下往上,逐步應用到畫面的最高層級。
    • Props 是唯讀的

      不管你使用 function 或是 class 來宣告 component,
      都絕不能修改自己的 props。
      所有的 React component 都必須像 Pure function 一般保護他的 props。
  • State 和生命週期

    封裝 Clock component 讓它可以真正的被重複使用。
    它將會設定本身的 timer 並且每秒更新一次。

    function Clock(props) {
    return (
    <div>
    <h1>Hello, world!</h1>
    <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
    );
    }

    function tick() {
    ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
    );
    }

    setInterval(tick, 1000);

    理想情況下,Clock 會自己更新,需要加入 state到 Clock component。
    State 類似於 prop,但它是私有且由 component 完全控制的。

    • 轉換 Function 成 Class

      透過以下 5 個步驟轉換一個 function component 像是 Clock 成為 class:

      1. 建立一個相同名稱並且繼承 React.Component 的 ES6 class。
      2. 加入一個 render() 的空方法。
      3. 將 function 的內容搬到 render() 方法。
      4. render() 內的 props 替換成 this.props
      5. 刪除剩下空的 function 宣告。
      class Clock extends React.Component {
      render() {
      return (
      <div>
      <h1>Hello, world!</h1>
      <h2>It is {this.props.date.toLocaleTimeString()}.</h2>
      </div>
      );
      }
      }
    • 加入 Local State 到 Class

      透過以下 3 個步驟將 date 從搬移到 state:

      1. render() 方法內的 this.props.date 替換成 this.state.date
      2. 加入一個 class constructor 並分配初始的 this.state
      3. <Clock /> element 中移除 date prop:
      class Clock extends React.Component {
      constructor(props) {
      super(props);
      this.state = {date: new Date()};
      }

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

      ReactDOM.render(
      <Clock />,
      document.getElementById('root')
      );
    • 加入生命週期方法到 Class

      每當 Clock render 到 DOM 的時候,我們想要設定一個 timer。在 React 中稱為「mount」。
      每當產生的 Clock DOM 被移除時,我們想要清除 timer。在 React 中稱為「unmount」。
      這些方法被稱為 「生命週期方法」
      componentDidMount() 方法會在 component 被 render 到 DOM 之後才會執行。
      componentWillUnmount() 生命週期方法內移除 timer。

      class Clock 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>
      );
      }
      }

      ReactDOM.render(
      <Clock />,
      document.getElementById('root')
      );
    • 正確的使用 State

      • 請不要直接修改 State
        // 錯誤
        // 唯一可以指定 `this.state` 值的地方是在 constructor。
        this.state.comment = 'Hello';

        // 正確
        this.setState({comment: 'Hello'});
      • State 的更新可能是非同步的
        React 可以將多個 setState() 呼叫批次處理為單一的更新,以提高效能。
        this.propsthis.state 可能是非同步的被更新,你不應該依賴它們的值來計算新的 state。
        // 錯誤
        this.setState({
        counter: this.state.counter + this.props.increment,
        });

        // 正確
        this.setState((state, props) => ({
        counter: state.counter + props.increment
        }));
      • State 的更新將會被 Merge
        呼叫 setState() 時,React 會 merge object 到目前的 state。
        constructor(props) {
        super(props);
        this.state = {
        posts: [],
        comments: []
        };
        }

        componentDidMount() {
        fetchPosts().then(response => {
        this.setState({
        posts: response.posts
        });
        });

        fetchComments().then(response => {
        this.setState({
        comments: response.comments
        });
        });
        }
    • 向下資料流

      Component 可以選擇將它的 state 做為 props 往下傳遞到它的 child component:

      <FormattedDate date={this.state.date} />

      FormattedDate component 會在它的 props 接收到 date

      function FormattedDate(props) {
      return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
      }

      任何 state 總是由某個特定的 component 所擁有,
      任何從 state 得到的資料或 UI,state 只能影響在 tree「以下」的 component。

  • 事件處理

    • 事件的名稱在 React 中都是 camelCase,而在 HTML DOM 中則是小寫。
    • 事件的值在 JSX 中是一個 function,而在 HTML DOM 中則是一個 string。
      在 HTML 中的語法:
      <button onclick="activateLasers()">
      Activate Lasers
      </button>

    在 React 中的語法:

    <button onClick={activateLasers}>
    Activate Lasers
    </button>

    在 React 中,不能夠使用 return false 來避免瀏覽器預設行為。
    必須明確地呼叫 preventDefault

    function Form() {
    function handleSubmit(e) {
    e.preventDefault();
    console.log('You clicked submit.');
    }

    return (
    <form onSubmit={handleSubmit}>
    <button type="submit">Submit</button>
    </form>
    );
    }

    請特別注意 this 在 JSX callback 中的意義。
    在 JavaScript 中,class 的方法在預設上是沒有被綁定(bound)的。
    把它傳遞給 onClick 的話,this 的值將會在該 function 被呼叫時變成 undefined。

    class Toggle extends React.Component {
    constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // 為了讓 `this` 能在 callback 中被使用,這裡的綁定是必要的:
    this.handleClick = this.handleClick.bind(this);
    }

    handleClick() {
    this.setState(prevState => ({
    isToggleOn: !prevState.isToggleOn
    }));
    }

    render() {
    return (
    <button onClick={this.handleClick}>
    {this.state.isToggleOn ? 'ON' : 'OFF'}
    </button>
    );
    }
    }

    ReactDOM.render(
    <Toggle />,
    document.getElementById('root')
    );
    • 將參數傳給 Event Handler

      在一個迴圈中,我們常常會需要傳遞一個額外的參數給 event handler。
      如果 id 是每一行的 ID 的話,下面兩種語法都可行:
      <button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
      <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
      以這兩個例子來說,e 這個參數所代表的 React 事件將會被當作 ID 之後的第二個參數被傳遞下去。
      在使用 arrow function 時,我們必須明確地將它傳遞下去,
      但若使用 bind 語法,未來任何的參數都將會自動被傳遞下去。
  • 條件 Render

    使用 JavaScript 中的運算子如 if 或者 三元運算子 來建立表示目前 state 的 element,
    然後讓 React 根據它們來更新 UI。

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

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

    // 建立一個 Greeting component,它會根據使用者是否已登入來顯示其中之一:
    function Greeting(props) {
    const isLoggedIn = props.isLoggedIn;
    if (isLoggedIn) {
    return <UserGreeting />;
    }
    return <GuestGreeting />;
    }

    ReactDOM.render(
    // 根據 isLoggedIn prop 的值來 render 不同的問候語。
    <Greeting isLoggedIn={false} />,
    document.getElementById('root')
    );
    • Element 變數

      可以用變數來儲存 element。

      function LoginButton(props) {
      return (
      <button onClick={props.onClick}>
      Login
      </button>
      );
      }

      function LogoutButton(props) {
      return (
      <button onClick={props.onClick}>
      Logout
      </button>
      );
      }

      // 建立一個名為 LoginControl 的 stateful component。
      // 它將根據目前的 state 來 render <LoginButton /> 或 < LogoutButton />。
      class LoginControl 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() {
      const isLoggedIn = this.state.isLoggedIn;
      let button;
      if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
      } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
      }

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

      ReactDOM.render(
      <LoginControl />,
      document.getElementById('root')
      );
    • Inline If 與 && 邏輯運算子

      透過大括號在 JSX 中嵌入表達式,包括 JavaScript 的 && 邏輯運算子,
      可以方便 render 有條件的 element:

      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')
      );

      能夠這樣做是因為在 JavaScript 中,
      true && expression 總是回傳 expression
      false && expression 總是回傳 false

      所以,當條件為 true 時,&& 右側的 element 會出現在輸出中,如果是 false,React 會忽略並跳過它。

      請注意,回傳 falsy expression 仍會導致 && 之後的 element 被忽略,
      但依舊回傳 falsy expression,在下面的範例中,render 將會回傳 <div>0</div>

      render() {
      const count = 0;
      return (
      <div>
      { count && <h1>Messages: {count}</h1>}
      </div>
      );
      }
    • Inline If-Else 與三元運算子

      另一個有條件 render element 的方式是透過 JavaScript 的三元運算子 condition ? true : false

      render() {
      const isLoggedIn = this.state.isLoggedIn;
      return (
      <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
      </div>
      );
      }
      render() {
      const isLoggedIn = this.state.isLoggedIn;
      return (
      <div>
      {isLoggedIn
      ? <LogoutButton onClick={this.handleLogoutClick} />
      : <LoginButton onClick={this.handleLoginClick} />
      }
      </div>
      );
      }
    • 防止 Component Render

      在少數的情況下,可能希望 component 隱藏自己本身,即便它是由另一個 component 被 render。
      可以透過回傳 null 而不是它的 render 輸出。

      在下面的範例中,<WarningBanner /> 的 render 取決於 warn prop 的值。
      如果 prop 是 false,它就不會 render。

      function WarningBanner(props) {
      if (!props.warn) {
      return null;
      }

      return (
      <div className="warning">
      Warning!
      </div>
      );
      }

      class Page extends React.Component {
      constructor(props) {
      super(props);
      this.state = {showWarning: true};
      this.handleToggleClick = this.handleToggleClick.bind(this);
      }

      handleToggleClick() {
      this.setState(state => ({
      showWarning: !state.showWarning
      }));
      }

      render() {
      return (
      <div>
      <WarningBanner warn={this.state.showWarning} />
      <button onClick={this.handleToggleClick}>
      {this.state.showWarning ? 'Hide' : 'Show'}
      </button>
      </div>
      );
      }
      }

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

      在 component 中回傳 null 並不會影響 component 的生命週期方法。
      例如 componentDidUpdate 依然可以被呼叫。

  • 列表與Key

    • Render 多個 Component

      可以建立一系列的 element 並用大括號 {} 將它們包含在 JSX 裡面
      用 JavaScript 的 map() function 迭代 numbers array,我們每次都會回傳一個 <li> element。
      最後,我們會把結果產生的 element array 設定為 listItems

      const numbers = [1, 2, 3, 4, 5];
      const listItems = numbers.map((number) =>
      <li>{number}</li>
      );

      把整個 listItems array 包含在一個 <ul> element 裡,然後 render 到 DOM 上面

      ReactDOM.render(
      <ul>{listItems}</ul>,
      document.getElementById('root')
      );
    • 基本列表 Component

      將上面的範例改寫為一個接收 numbers array 並輸出一個沒有排序的 element 列表的 component。

      function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
      <li>{number}</li>
      );
      return (
      <ul>{listItems}</ul>
      );
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
      );

      當執行這段程式碼時,會收到一個關於應該提供 key 給每一個列表項目的警告。
      「key」是當你在建立一個 element 列表時必須使用的特殊的 string attribute。

      為 numbers.map() 列表中的每個項目分配一個 key,並修正遺漏 key 的問題。

      function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
      <li key={number.toString()}>
      {number}
      </li>
      );
      return (
      <ul>{listItems}</ul>
      );
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
      );
    • Key

      Key 幫助 React 分辨哪些項目被改變、增加或刪除。
      在 array 裡面的每個 element 都應該要有一個 key,如此才能給予每個 element 一個固定的身份:

      const numbers = [1, 2, 3, 4, 5];
      const listItems = numbers.map((number) =>
      <li key={number.toString()}>
      {number}
      </li>
      );

      選擇 key 最佳的方法是在列表中使用唯一識別字串來區別 sibling 項目。
      通常,會使用資料的 ID 作為 key:

      const todoItems = todos.map((todo) =>
      <li key={todo.id}>
      {todo.text}
      </li>
      );

      當 render 的項目沒有固定的 ID ,且也沒有更好的辦法時,
      可以使用項目的索引做為 key:

      // 請在項目沒有固定的 ID 時才這樣做
      const todoItems = todos.map((todo, index) =>
      <li key={index}>
      {todo.text}
      </li>
      );

      不建議使用索引作為 key,尤其如果項目的順序會改變的話。
      這會對效能產生不好的影響,也可能會讓 component state 產生問題。

    • 用 Key 抽離 Component

      Key 只有在周遭有 array 的情境中才有意義。
      例如,如果要抽離一個 ListItem component 的話,應該把 key 放在 array 裡的 element 上,
      而不是把它放在 ListItem 裡面的

    • element 上。

      範例:Key 的錯誤使用方式

      function ListItem(props) {
      const value = props.value;
      return (
      // 錯!你不需要在這裡指出 key:
      <li key={value.toString()}>
      {value}
      </li>
      );
      }

      function NumberList(props) {
      const numbers = props.numbers;

      const listItems = numbers.map((number) =>
      <ListItem value={number} /> // 應該要在這裡指出 key:
      );
      return (
      <ul>
      {listItems}
      </ul>
      );
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
      );

      範例:Key 的正確使用方式

      function ListItem(props) {
      // 不需要在這裡指出 key:
      return <li>{props.value}</li>;
      }

      function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
      <ListItem key={number.toString()} value={number} /> // 正確!Key 應該在 array 內被指定。
      );
      return (
      <ul>
      {listItems}
      </ul>
      );
      }

      const numbers = [1, 2, 3, 4, 5];
      ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
      );

      一個好的經驗法則是,在 map() 呼叫中的每個 element 都會需要 key。

    • Key 必須在 Sibling 中是唯一的

      在 array 中使用的 key 應該要是唯一的值。然而,它們不必在全域中唯一。
      當產生兩個不同的 array 時,仍然可以使用相同的 key:

      function Blog(props) {
      const sidebar = (
      <ul>
      {props.posts.map((post) =>
      <li key={post.id}>
      {post.title}
      </li>
      )}
      </ul>
      );
      const content = props.posts.map((post) =>
      <div key={post.id}>
      <h3>{post.title}</h3>
      <p>{post.content}</p>
      </div>
      );
      return (
      <div>
      {sidebar}
      <hr />
      {content}
      </div>
      );
      }

      const posts = [
      {id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
      {id: 2, title: 'Installation', content: 'You can install React from npm.'}
      ];
      ReactDOM.render(
      <Blog posts={posts} />,
      document.getElementById('root')
      );

      Key 的功能是提示 React,但它們不會被傳遞到你的 component。
      如果在 component 中需要同樣的值,可以直接把這個值用一個不同的名稱作為 prop 傳下去:

      const content = posts.map((post) =>
      <Post
      key={post.id}
      id={post.id}
      title={post.title} />
      );

      Post component 可以讀取 props.id,但不能讀取 props.key

    • 在 JSX 中嵌入 map()

      在上面的例子中,我們宣告了另一個 listItems 變數並把它包含在 JSX 中:

      function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
      <ListItem key={number.toString()} value={number} />
      );
      return (
      <ul>
      {listItems}
      </ul>
      );
      }

      JSX 能在大括號中嵌入任何表達式,所以能夠 inline map() 的結果:

      function NumberList(props) {
      const numbers = props.numbers;
      return (
      <ul>
      {numbers.map((number) =>
      <ListItem key={number.toString()} value={number} />
      )}
      </ul>
      );
      }

      有時候這會產生更乾淨的程式碼,但這種風格也可能被濫用。
      就像 JavaScript 一樣,是否要將變數抽取出來以增加可讀性完全是看你的決定。
      請記得,如果 map() 的程式碼層級變得過度巢狀,也許就是使用 抽離component 的時候了。

  • 表單

    • Controlled Component

      在 HTML 中,表單的 element 像是 <input><textarea><select> 通常會維持它們自身的 state,並根據使用者的輸入來更新 state。
      在 React 中,可變的 state 通常是被維持在 component 中的 state property,並只能以 setState() 來更新。
      render 表單的 React component 同時也掌握了後續使用者的輸入對表單帶來的改變。
      像這樣一個輸入表單的 element,被 React 用這樣的方式來控制它的值,就被稱為「controlled component」。

      class NameForm 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>
      );
      }
      }

      由於 value attribute 是被設定在表單 element 上,顯示的 value 會永遠是 this.state.value,這使得 React 的 state 成為了資料來源。
      由於 handleChange 在每一次鍵盤被敲擊時都會被執行,並更新 React 的 state,因此被顯示的 value 將會在使用者打字的同時被更新。

      在這樣的 controlled component 中,顯示的 value 始終由 React 的 state 驅動,
      雖然這意味著必須寫更多的 code,但現在同時可以將 value 傳遞給其他的 UI element,或是從其他 event handler 重置。

    • Textarea 標籤

      在 HTML 中,一個 <textarea> 的 element 是經由它的 children 來定義它的文字:

      <textarea>
      Hello there, this is some text in a text area
      </textarea>

      在 React 中,<textarea> 則是使用一個 value 的 attribute。
      如此一來,一個使用 <textarea> 的表單可以使用非常類似單行的 input 方法來寫成:

      class EssayForm extends React.Component {
      constructor(props) {
      super(props);
      this.state = {
      value: 'Please write an essay about your favorite DOM element.'
      };

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

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

      handleSubmit(event) {
      alert('An essay was submitted: ' + this.state.value);
      event.preventDefault();
      }

      render() {
      return (
      <form onSubmit={this.handleSubmit}>
      <label>
      Essay:
      <textarea value={this.state.value} onChange={this.handleChange} />
      </label>
      <input type="submit" value="Submit" />
      </form>
      );
      }
      }
    • Select 標籤

      在 HTML 中,<select> 會建立一個下拉式選單。
      例如,這個 HTML 會建立一個有各種水果的下拉式選單:

      <select>
      <option value="grapefruit">Grapefruit</option>
      <option value="lime">Lime</option>
      <option selected value="coconut">Coconut</option>
      <option value="mango">Mango</option>
      </select>

      椰子的選項是一開始就被選定的,因為它有一個 selected attribute。
      但是在 React 中並不是用 selected attribute,而是在 select 的標籤上用一個 value attribute。
      對一個 controlled component 來說這是比較方便的,因為只需要在一個地方更新它。例如:

      class FlavorForm extends React.Component {
      constructor(props) {
      super(props);
      this.state = {value: 'coconut'};

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

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

      handleSubmit(event) {
      alert('Your favorite flavor is: ' + this.state.value);
      event.preventDefault();
      }

      render() {
      return (
      <form onSubmit={this.handleSubmit}>
      <label>
      Pick your favorite flavor:
      <select value={this.state.value} onChange={this.handleChange}>
      <option value="grapefruit">Grapefruit</option>
      <option value="lime">Lime</option>
      <option value="coconut">Coconut</option>
      <option value="mango">Mango</option>
      </select>
      </label>
      <input type="submit" value="Submit" />
      </form>
      );
      }
      }

      整體來說,這使得 <input type="text"><textarea><select> 使用起來都很類似。
      它們全都會接收一個 controlled component 時會使用到的 value attribute。

    • 檔案 input 標籤

      在 HTML 中,<input type="file"> 讓使用者從它們的儲存裝置中選擇一個至多個檔案,
      並把它們上傳到伺服器或透過 File API 被 JavaScript 處理。

      <input type="file" />

      由於它的值是唯讀,它在 React 中是一個 uncontrolled component。

    • 處理多個輸入

      當需要處理多個 controlled input element,可以在每個 element 中加入一個 name attribute,
      並讓 handler function 選擇基於 event.target.name 的值該怎麼做:

參考資源:

/... 未完待續 .../