一杯茶的時間,上手 React 框架開發

React(也被稱爲 React.js 或者 ReactJS)是一個用於構建用戶界面的 JavaScript 庫。起源於 Facebook 內部項目,最初用來架設 Instagram 的網站,並於 2013 年 5 月開源。React 性能較高,而且它的聲明式、組件化特性讓編寫代碼變得簡單,隨着 React 社區的發展,愈來愈多的人投入 React 的學習和開發,使得 React 不只能夠用來開發 Web 應用,還能開發桌面端應用,TV應用,VR應用,IoT應用等,所以 React 還具備一次學習,隨處編寫的特性。本教程將帶你快速入門 React 開發,經過 20-30 分鐘的學習,你不只能夠了解 React 的基礎概念,並且能開發出一個待辦事項小應用,還在想什麼了?立刻學起來吧!本文全部代碼已放在 GitHub 倉庫中。html

此教程屬於 React 前端工程師學習路線的一部分,點擊可查看所有內容。前端

Hello, World

咱們將構建什麼?

在這篇教程中,咱們將展現給你如何使用 React 構建一個待辦事項應用,下面最終項目的展現成果:node

你也能夠在這裏看到咱們最後構建的結果:最終結果。若是你如今對代碼還不是很理解,或者你還不熟悉代碼語法,別擔憂!這篇教程的目標就是幫助你理解 React 和它的語法。react

咱們推薦你在繼續閱讀這篇教程以前先熟悉一下這個待辦事項,你甚至能夠嘗試添加幾個待辦事項!你可能注意到當你添加了2個待辦事項以後,會出現不一樣的顏色;這就是 React 中條件渲染的魅力。git

當你熟悉了這個待辦事項以後你就能夠關閉它了。在這篇教程的學習中,咱們將從一個 Hello World 代碼模板開始,而後帶領你初始化開發環境,這樣你就能夠開始構建這個待辦事項了。github

你將學到什麼?

你將學習全部 React 的基礎概念,其中又分爲三個部分:數據庫

  • 編寫組件相關:包括 JSX 語法、Component、Props
  • 組件的交互:包括 State 和生命週期
  • 組件的渲染:包括列表和 Key、條件渲染
  • 和 DOM & HTML 相關:包括事件處理、表單。

前提條件

咱們假設你熟系 HTML 和 JavaScript,但即便你是從其餘編程語言轉過來的,你也能看懂這篇教程。咱們還假設你對一些編程語言的概念比較熟悉,好比函數、對象、數組,若是對類瞭解就更好了。npm

若是你須要複習 JavaScript,咱們推薦你閱讀這篇指南。你可能注意到了咱們使用了一些 ES6 的特性 -- 一個最近的 JavaScript 版本。在這篇教程,咱們會使用 arrow functionsclasses,和 const。你可使用 Babel REPL 來檢查 ES6 代碼編譯以後的結果。編程

環境準備

首先準備 Node 開發環境,訪問 Node 官方網站下載並安裝。打開終端輸入以下命令檢測 Node 是否安裝成功:數組

node -v # v10.16.0
npm -v # 6.9.0
複製代碼

注意

Windows 用戶須要打開 cmd 工具,Mac 和 Linux 是終端。

若是上面的命令有輸出且無報錯,那麼表明 Node 環境安裝成功。接下來咱們將使用 React 腳手架 -- Create React App(簡稱 CRA)來初始化項目,同時這也是官方推薦初始化 React 項目的最佳方式。

在終端中輸入以下命令:

npx create-react-app my-todolist
複製代碼

等待命令運行完成,接着輸入以下命令開啓項目:

cd my-todolist && npm start
複製代碼

CRA 會自動開啓項目並打開瀏覽器,你應該能夠看到下面的結果:

🎉🎉🎉 恭喜你!成功建立了第一個 React 應用!

如今 CRA 初始化的項目裏有不少無關的內容,爲了開始接下來的學習,咱們還須要作一點清理工做。首先在終端中按 ctrl + c 關閉剛剛運行的開發環境,而後在終端中依次輸入以下的命令:

# 進入 src 目錄
cd src

# 若是你在使用 Mac 或者 Linux:
rm -f *

# 或者,你在使用 Windows:
del *

# 而後,建立咱們將學習用的 JS 文件
# 若是你在使用 Mac 或者 Linux:
touch index.js

# 或者,你在使用 Windows
type nul > index.js

# 最後,切回到項目目錄文件夾下
cd ..
複製代碼

此時若是在終端項目目錄下運行 npm start 會報錯,由於咱們的 index.js 尚未內容,咱們在終端中使用 ctrl +c 關閉開發服務器,而後使用編輯器打開項目,在剛剛建立的 index.js 文件中加入以下代碼:

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

class App extends React.Component {
  render() {
    return <div>Hello, World</div>;
  }
}

ReactDOM.render(<App />, document.getElementById("root")); 複製代碼

咱們看到 index.js 裏面的代碼分爲三個部分。

首先是一系列導包,咱們導入了 react 包,並命名爲 React,導入了 react-dom 包並命名爲 ReactDOM。對於包含 React 組件(咱們將在以後講解)的文件都必須在文件開頭導入 React。

而後咱們定義了一個 React 組件,命名爲 App,繼承自 React.Component,組件的內容咱們將會在後面進行講解。

接着咱們使用 ReactDOM 的 render 方法來渲染剛剛定義的 App 組件,render方法接收兩個參數,第一個參數爲咱們的 React 根級組件,第二個參數接收一個 DOM 節點,表明咱們將把和 React 應用掛載到這個 DOM 節點下,進而渲染到瀏覽器中。

注意

上面代碼的三個部分中,第一部分和第三部分在整篇教程中是不會修改的,同時在編寫任意 React 應用,這兩個部分都是必須的。後面全部涉及到的代碼修改都是關於第二部分代碼的修改,或者是在第一部分到第三部分之間插入或刪除代碼。

保存代碼,在終端中使用 npm start 命令開啓開發服務器,如今瀏覽器應該會顯示以下內容:

準備工做已經就緒!

你可能對上面的代碼細節還不是很清楚,別擔憂,咱們將立刻帶你領略 React 的神奇世界!

JSX 語法

首先咱們來看一下 React 引覺得傲的特性之一 -- JSX。它容許咱們在 JS 代碼中使用 XML 語法來編寫用戶界面,使得咱們能夠充分的利用 JS 的強大特性來操做用戶界面。

一個 React 組件的 render 方法中 return 的內容就爲這個組件所將渲染的內容。好比咱們如今的代碼:

render() {
    return <div>Hello, World</div>;
}
複製代碼

這裏的 <div>Hello, World</div> 是一段 JSX 代碼,它最終會被 Babel 轉譯成下面這段 JS 代碼:

React.createElement(
  'div',
  null,
  'Hello, World'
)
複製代碼

React.createElement() 接收三個參數:

  • 第一個參數表明 JSX 元素標籤。
  • 第二個參數表明這個 JSX 元素接收的屬性,它是一個對象,這裏由於咱們的 div 沒有接收任何屬性,因此它是 null
  • 第三個參數表明 JSX 元素包裹的內容。

React.createElement() 會對參數作一些檢查確保你寫的代碼不會產生 BUG,它最終會建立一個相似下面的對象:

{
  type: 'div',
  props: {
    children: 'Hello, World'
  }
};
複製代碼

這些對象被稱之爲 「React Element」。你能夠認爲它們描述了你想要在屏幕上看到的內容。React 將會接收這些對象,使用它們來構建 DOM,而且對它們進行更新。

注意

咱們推薦你使用 「Babel」 查看 JSX 的編譯結果。

App 組件最終返回這段 JSX 代碼,因此咱們使用 ReactDOM 的 render 方法渲染 App 組件,最終顯示在屏幕上的就是 Hello, World" 內容。

JSX 做爲變量使用

由於 JSX 最終會被編譯成一個 JS 對象,因此咱們能夠把它當作一個 JS 對象使用,它享有和一個 JS 對象同等的地位,好比能夠將其賦值給一個變量,咱們修改上面代碼中的 render 方法以下:

render() {
  const element = <div>Hello, World</div>;
  return element;
}
複製代碼

保存代碼,咱們發現瀏覽器中渲染的內容和咱們以前相似。

在 JSX 中使用變量

咱們可使用大括號 {} 在 JSX 中動態的插入變量值,好比咱們修改 render 方法以下:

render() {
  const content = "World";
  const element = <div>Hello, {content}</div>;
  return element;
}
複製代碼

保存代碼,發現瀏覽器中效果依然不變。

JSX 中使用 JSX

咱們能夠在 JSX 中再包含 JSX,這樣咱們編寫任意層次的 HTML 結構:

render() {
    const element = <li>Hello, World</li>
    return (
      <div> <ul> {element} </ul> </div>
    )
  }
複製代碼

JSX 中添加節點屬性

咱們能夠像在 HTML 中同樣,給元素標籤加上屬性,只不過咱們須要遵照駝峯式命名法則,好比在 HTML 上的屬性 data-index 在 JSX 節點上要寫成 dataIndex

const element = <div dataIndex="0">Hello, World</div>;
複製代碼

注意

在 JSX 中全部的屬性都要更換成駝峯式命名,好比 onclick 要改爲 onClick,惟一比較特殊的就是 class,由於在 JS 中 class 是保留字,咱們要把 class 改爲 className

const element = <div className="app">Hello, World</div>;
複製代碼

實戰

咱們使用這一節講解的 JSX 知識,來繼續完成咱們的待辦事項應用。

在編輯器中打開 src/index.js ,對 App 組件作以下改變:

class App extends React.Component {
  render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    return (
      <ul> <li>Hello, {todoList[0]}</li> <li>Hello, {todoList[1]}</li> <li>Hello, {todoList[2]}</li> <li>Hello, {todoList[3]}</li> </ul>
    );
  }
}
複製代碼

能夠看到,咱們使用 const 定義了一個 todoList 數組常量,而且在 JSX 中使用 {} 進行動態插值,插入了數組的四個元素。

最後保存代碼,瀏覽器中的效果應該是這樣的:

提示

無需關閉剛纔使用 npm start 開啓的開發服務器,修改代碼後,瀏覽器中的內容將會自動刷新!

你可能注意到了咱們手動獲取了數組的四個值,而後逐一的用 {} 語法插入到 JSX 中並最終渲染,這樣作還比較原始,咱們將在後面列表和 Key 小節中簡化這種寫法。

在這一小節中,咱們瞭解了 JSX 的概念,而且實踐了相關的知識。咱們還提出了組件的概念,可是並無深刻講解它,在下一小節中咱們將詳細地講解組件的知識。

Component

React 的核心特色之一就是組件化,即咱們將巨大的業務邏輯拆分紅一個個邏輯清晰的小組件,而後經過組合這些組件來完成業務功能。

React 提供兩種組件寫法:1)函數式組件 2)類組件。

函數式組件

在 React 中,函數式組件會默認接收一個 props 參數,而後返回一段 JSX:

function Todo(props) {
  return <li>Hello, 圖雀</li>;
}
複製代碼

關於 props 咱們將在下一節中講解。

類組件

經過繼承自 React.Component 的類來表明一個組件。

class Todo extends React.Component {
  render() {
    return <li>Hello, 圖雀</li>;
  }
}
複製代碼

咱們發現,在類組件中,咱們須要在 render 方法裏面返回須要渲染的 JSX。

組件組合

咱們能夠組合不一樣的組件來完成複雜的業務邏輯:

class App extends React.Component {
  render() {
    return (
      <ul> <Todo /> <Todo /> </ul>
    );
  }
}
複製代碼

在上面的代碼中,咱們在類組件 App 中使用了咱們以前定義的 Todo 組件,咱們看到,組件以 <Component /> 的形式使用,好比 Todo 組件使用時爲 <Todo />,咱們在 Todo 組件沒有子組件時使用這種寫法;當 Todo 組件須要包含子組件時,咱們須要寫成下面的形式:

class App extends React.Component {
  render() {
    return (
      <ul> <Todo>Hello World</Todo> <Todo>Hello Tuture</Todo>> </ul>
    );
  }
}
複製代碼

組件渲染

咱們在第一節中講到,經過 ReactDOM.render 方法接收兩個參數:1)根組件 2) 待掛載的 DOM 節點,能夠將組件的內容渲染到 HTML 中。

ReactDOM.render(<App />, document.getElementById('root')); 複製代碼

實戰

咱們運用在這一節中學到的組件知識來繼續完成咱們的待辦事項應用。咱們編寫一個 Todo 類組件,用於表明單個待辦事項,而後在 App 類組件中使用 Todo 組件。

打開 src/index.js 文件,實現 Todo 組件,並調整 App 組件代碼以下:

class Todo extends React.Component {
  render() {
    return <li>Hello, 圖雀</li>;
  }
}

class App extends React.Component {
  render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    return (
      <ul> <Todo /> <Todo /> <Todo /> <Todo /> </ul>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root")); 複製代碼

保存代碼,而後你應該能夠在瀏覽器中看到以下結果:

你可能注意到咱們暫時沒有使用以前定義的 todoList 數組,而是使用了四個相同的 Todo 組件,咱們使用繼承自 React.Component 類的形式定義 Todo 組件,而後在組件的 render 中返回了 <li>Hello, 圖雀</li>,因此最終瀏覽器中會渲染四個 "Hello, 圖雀"。而且由於 Todo 組件不須要包含子組件,因此咱們寫成了 <Todo /> 的形式。

如今4個待辦事項都是同樣的內容,這有點單調,你可能會想,若是能夠像調用函數那樣能夠經過傳參對組件進行個性化定製就行了,你的想法是對的!咱們將在下一節中引出 props,它容許你給組件傳遞內容,從而進行個性化內容定製。

Props

React 爲組件提供了 Props,使得在使用組件時,能夠給組件傳入屬性進行個性化渲染。

函數式組件中使用 Props

函數式組件默認接收 props 參數,它是一個對象,用於保存父組件傳遞下來的內容:

function Todo(props) {
  return (
    <li>Hello, {props.content}</li>
  )
}

<Todo content="圖雀" />
複製代碼

咱們給 Todo 函數式組件傳遞了一個 content 屬性, 它的值爲 "圖雀" ,全部傳遞的屬性都會合並進 props 對象中,而後傳遞給 Todo 組件,這裏 props 對象是這樣的 props = { content: "圖雀" } ,若是咱們再傳遞一個屬性:

<Todo content="圖雀" from="圖雀社區" />
複製代碼

最終 props 對象就會變成這樣:props={ content: "圖雀", from: "圖雀社區" }

注意

若是給組件傳遞 key 屬性是不會併入 props 對象中的,因此咱們在子組件中也取不到 key 屬性,咱們將在列表和 Key 一節 中詳細講解。

類組件中使用 Props

類組件中基本和函數式組件中的 Props 保持一致,除了是經過 this.props 來獲取父組件傳遞下來的屬性內容:

class Todo extends React.Component {
  render() {
    return <li>Hello, {this.props.content}</li>;
  }
}

<Todo content="圖雀" />
複製代碼

實戰

咱們運用這一節中學到的 Props 知識來繼續完成咱們的待辦事項應用。

打開 src/index.js 文件,分別調整 Todo 和 App 組件,修改後代碼以下:

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

class Todo extends React.Component {
  render() {
    return <li>Hello, {this.props.content}</li>;
  }
}

class App extends React.Component {
  render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    return (
      <ul>
        <Todo content={todoList[0]} />
        <Todo content={todoList[1]} />
        <Todo content={todoList[2]} />
        <Todo content={todoList[3]} />
      </ul>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
複製代碼

注意到咱們又從新開始使用以前定義的 todoList 數組,而後給每一個 Todo 組件傳遞一個 content 屬性,分別賦值數組的每一項,最後在 Todo 組件中使用咱們傳遞下來的 content 屬性。

保存修改的內容,你應該能夠在瀏覽器中看到以下的內容:

能夠看到,咱們的內容又回來了,和咱們以前在 JSX 一節 中看到的內容同樣,可是這一次咱們成功使用了組件來渲染接收到的 Props 內容。

State 和生命週期

React 經過給類組件提供 State 來創造交互式的內容 -- 即內容能夠在渲染以後發生變化。

定義 State

經過在類組件中添加 constructor 方法,並在其中定義和初始化 State:

constructor(props) {
    super(props);

    this.state = {
      todoList: ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"]
    };
  }
複製代碼

這裏 constructor 方法接收的 props 屬性就是咱們在上一節中講到的那個 props;而且 React 約定每一個繼承自 React.Component 的組件在定義 constructor 方法時,要在方法內首行加入 super(props)

接着咱們 this.state 來定義組件的 state,並使用 { todoList: ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"] } 對象來初始化 state。

使用 State

咱們能夠在一個組件中的多處地方經過 this.state 的方式來使用 state,好比咱們在這一節中將講到的生命週期函數中,好比在 render 方法中:

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

    this.state = {
      todoList: ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"]
    };
  }

  render() {
    return (
      <ul>
        <Todo content={this.state.todoList[0]} />
        <Todo content={this.state.todoList[1]} />
        <Todo content={this.state.todoList[2]} />
        <Todo content={this.state.todoList[3]} />
      </ul>
    );
  }
}
複製代碼

咱們經過 this.state.todoList 能夠獲取咱們在 constructor 方法中定義的 state,能夠看到,咱們使用 this.state.todoList[0] 的方式替代了以前的 todoList[0]

更新 State

咱們經過 this.setState 方法來更新 state,從而使得網頁內容在渲染以後還能變化:

this.setState({ todoList: newTodoList });
複製代碼

注意

關於 this.setState 咱們須要注意如下幾點:

1)這裏不可以經過直接修改 this.state 的方式來更新 state:

// 錯誤的
this.state.todoList = newTodoList;
複製代碼

2)State 的更新是合併更新的:

好比原 state 是這樣的:

constructor(props) {
  super(props);
  this.state = {
    todoList: [],
    nowTodo: '',
  };
}
複製代碼

而後你調用 this.setState() 方法來更新 state:

this.setState({ nowTodo: "Hello, 圖雀" });
複製代碼

React 將會合並更新,即將 nowTodo 的新內容合併進原 this.state,當更新以後,咱們的 this.state 將會是下面這樣的:

this.state = { todoList: [], nowTodo: "Hello, 圖雀" };
複製代碼

不會由於只單獨設置了 nowTodo 的值,就將 todoList 給覆蓋掉。

生命週期函數

React 提供生命週期函數來追蹤一個組件從建立到銷燬的全過程。主要包含三個方面:

  • 掛載(Mounting)
  • 更新(Updating)
  • 卸載(Unmounting)

一個簡化版的生命週期的圖示是這樣的:

注意

React 生命週期相對而言比較複雜,咱們這裏不會詳細講解每一個部分,上面的圖示用戶能夠試着瞭解一下,對它有個大概的印象。

查看完整版的生命週期圖示請參考這個連接:點擊查看

這裏咱們主要講解掛載和卸載裏面經常使用的生命週期函數。

掛載

其中掛載中主要經常使用的有三個方法:

  • constructor()
  • render()
  • componentDidMount()

constructor() 在組件建立時調用,若是你不須要初始化 State ,即不須要 this.state = { ... } 這個過程,那麼你不須要定義這個方法。

render() 方法是掛載時用來渲染內容的方法,每一個類組件都須要一個 render 方法。

componentDidMount() 方法是當組件掛載到 DOM 節點中以後會調用的一個方法,咱們一般在這裏發起一些異步操做,用於獲取服務器端的數據等。

卸載

卸載只有一個方法:

  • componentWillUnmount()

componentWillUnmount 是當組件從 DOM 節點中卸載以前會調用的方法,咱們通常在這裏面銷燬定時器等會致使內存泄露的內容。

實戰

咱們運用在這一節中學到的 State 和生命週期知識來繼續完成咱們的待辦事項應用。

打開 src/index.js,修改代碼以下:

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

const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];

class Todo extends React.Component {
  render() {
    return <li>Hello, {this.props.content}</li>;
  }
}

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

    this.state = {
      todoList: []
    };
  }

  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        todoList: todoList
      });
    }, 2000);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  render() {
    return (
      <ul>
        <Todo content={this.state.todoList[0]} />
        <Todo content={this.state.todoList[1]} />
        <Todo content={this.state.todoList[2]} />
        <Todo content={this.state.todoList[3]} />
      </ul>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("root"));
複製代碼

能夠看到咱們主要改動了五個部分:

  • 將 todoList 移動到組件外面。
  • 定義 constructor 方法,而且經過設置 this.state = { todoList: [] } 來初始化組件的 State,這裏咱們將 todoList 初始化爲空數組。
  • 添加 componentDidMount 生命週期方法,當組件掛載到 DOM 節點以後,設置一個時間爲 2S 的定時器,並賦值給 this.timer,用於在組件卸載時銷燬定時器。等到 2S 以後,使用 this.setState({ todoList: todoList }) 來使用咱們剛剛移動到組件外面的 todoList 來更新組件的 this.state.todoList
  • 添加 componentWillUnMount 生命週期方法,在組件卸載時,經過 clearTimeout(this.timer) 來清除咱們以前設置的定時器,防止出現內存泄露。

保存修改的代碼,咱們應該會看到瀏覽器中有一個內容更新的過程,在組件剛剛建立並掛載時,瀏覽器屏幕上應該是這樣的:

由於咱們在 this.state 初始化時,將 todoList 設置爲了空數組,因此一開始 "Hello" 後面的 this.props.content 內容爲空,咱們就出現了四個 "Hello, "

而後當過了 2S 以後,咱們能夠看到熟悉的內容出現了:

由於在過了 2S 以後,咱們在定時器的回調函數中將 todoList 設置爲了定義在組件外面的那個 todoList 數組,它有四個元素,因此顯示在瀏覽器上面的內容又是咱們以前的樣子。

恭喜你!成功建立了本身第一個交互式的組件!

讀到這裏,你有可能有點累了,試着離開座椅,活動活動,喝杯咖啡,精彩稍後繼續 🙃

列表和 Key

目前咱們有四個 Todo 組件,咱們是一個一個取值而後渲染,這顯得有點原始,而且不可擴展,由於當咱們的 todoList 數組很大的時候(好比 100 個元素),一個一個獲取就顯得不切實際了,這個時候咱們就須要循環介入了。

渲染組件列表

JSX 容許咱們渲染一個列表:

render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    const renderTodoList = todoList.map((todo) => (
      <Todo content={todo} /> )); return ( <ul> {renderTodoList} </ul> ); } 複製代碼

咱們經過對 todoList 進行 map 遍歷,返回了一個 Todo 列表,而後使用 {} 插值語法渲染這個列表。

固然咱們能夠在 JSX 中使用表達式,因此上面的代碼能夠寫成這樣:

render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    return (
      <ul> {todoList.map((todo) => ( <Todo content={todo} /> ))} </ul> ); } 複製代碼

加上 Key

React 要求給列表中每一個組件加上 key 屬性,用於標誌在列表中這個組件的身份,這樣當列表內容進行了修改:增長或刪除了元素時,React 能夠根據 key 屬性高效的對列表組件進行建立和銷燬操做:

render() {
    const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];
    return (
      <ul> {todoList.map((todo, index) => ( <Todo content={todo} key={index} /> ))} </ul> ); } 複製代碼

這裏咱們使用了列表的 index 做爲組件的 key 值,React 社區推薦的最佳實踐方式是使用列表數據元素的惟一標識符做爲 key 值,若是你的數據是來自數據庫獲取,那麼列表元素數據的主鍵能夠做爲 key

這裏的 key 值不會做爲 props 傳遞給子組件,React 會在編譯組件時將 key 值從 props 中排除,即最終咱們的第一個 Todo 組件的 props 以下:

props = { content: "圖雀" }
複製代碼

而不是咱們認爲的:

props = { content: "圖雀", key: 0 }
複製代碼

實戰

咱們運用這一節學到的列表和 Key 的知識來繼續完成咱們的待辦事項應用。

打開 src/index.js,代碼修改以下:

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

const todoList = ["圖雀", "圖雀寫做工具", "圖雀社區", "圖雀文檔"];

class Todo extends React.Component {
  render() {
    return <li>Hello, {this.props.content}</li>;
  }
}

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

    this.state = {
      todoList: []
    };
  }

  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        todoList: todoList
      });
    }, 2000);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  render() {
    return (
      <ul> {this.state.todoList.map((todo, index) => ( <Todo content={todo} key={index} /> ))} </ul> ); } } ReactDOM.render(<App />, document.getElementById("root")); 複製代碼

能夠看到,咱們將以前的手動獲取元素渲染改爲了使用內嵌表達式的方式,經過對 this.state.todoList 列表進行 map 操做生成Todo 組件列表,而後使用列表的 index 做爲組件的 key 值,最後渲染。

保存內容,查看瀏覽器裏面的內容,咱們能夠看到內容會有一個變化的過程,一開始是這樣的:

你會發現一片空白,而後過了 2S 變成了下面這樣:

這是由於,一開始 this.state.todoList 在 constructor 中初始化時是空數組, this.state.todoList 進行 map 操做時返回空數組,因此咱們的瀏覽器中沒有內容,當組件掛載以後,等待 2S,咱們更新 this.state.todoList 內容,就看到瀏覽器裏面得到了更新。

條件渲染

在 React 中,咱們能夠根據不一樣的狀況,渲染不一樣的內容,這也被成爲條件渲染。

if-else 條件渲染

render() {
    if (this.props.content === "圖雀") {
      return <li>你好, {this.props.content}</li>;
    } else {
      return <li>Hello, {this.props.content}</li>;
    }
  }
複製代碼

在上面的代碼中,咱們判斷 this.props.content 的內容,當內容爲 "圖雀" 時,咱們渲染 "你好, 圖雀",對於其餘內容咱們依然渲染 "Hello, 圖雀"

三元表達式條件渲染

咱們還能夠直接在 JSX 中使用三元表達式進行條件渲染:

render() {
    return this.props.content === "圖雀"? (
      <li>你好, {this.props.content}</li>
    ) : (
      <li>Hello, {this.props.content}</li>
    );
  }
複製代碼

固然三元表達式還能夠用來條件渲染不一樣的 React 元素屬性:

render() {
    return (
      <li className={this.state.isClicked ? 'clicked' : 'notclicked'}>Hello, {this.props.content}</li>
    )
  }
複製代碼

上面咱們判斷組件的 this.state.isClicked 屬性,若是 this.state.isClicked 屬性爲 true,那麼咱們最終渲染 "clicked" 類:

render() {
    return (
      <li className="clicked"}>Hello, {this.props.content}</li>
    )
  }
複製代碼

若是 this.state.isClickedfalse,那麼最終渲染 notclicked 類:

render() {
    return (
      <li className="notclicked">Hello, {this.props.content}</li>
    )
  }
複製代碼

實戰

咱們運用本節學到的知識繼續完成咱們的待辦事項應用。

打開 src/index.js ,對 Todo 和 App 組件做出以下修改:

class Todo extends React.Component {
  render() {
    if (this.props.index % 2 === 0) {
      return <li style={{ color: "red" }}>Hello, {this.props.content}</li>;
    }

    return <li>Hello, {this.props.content}</li>;
  }
}

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

    this.state = {
      todoList: []
    };
  }

  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        todoList: todoList
      });
    }, 2000);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  render() {
    return (
      <ul> {this.state.todoList.map((todo, index) => ( <Todo content={todo} key={index} index={index} /> ))} </ul> ); } } 複製代碼

咱們在首先在 App 組件中給 Todo 組件傳入了一個 index 屬性,而後在 Todo 組件的 render 方法中,對 this.props.index 進行判斷,若是爲偶數,那麼渲染一個紅色的文字,若是爲奇數則保持不變。

這裏咱們經過給 li 元素的 style 屬性賦值一個對象來實如今 JSX 中設置元素的 CSS 屬性,咱們能夠經過一樣的方式設置任何 CSS 屬性:

// 黑底紅字的 Hello, 圖雀
<li style={{ color: "red", backgroundColor: "black"}}>Hello, 圖雀</li>
複製代碼

注意

這裏有一點須要作一下改變,就是像 background-color 這樣的屬性,要寫成駝峯式 backgroundColor。對應的好比 font-size,也要寫成 fontSize

保存代碼,你應該能夠在瀏覽器中看到以下內容:

咱們看到瀏覽器中的效果確實是在偶數項(數組中 0 和 2 項)變成了紅色的字體,而(數組中 1 和 3 項)仍是以前的黑色樣式。

事件處理

在 React 元素中處理事件和在 HTML 中相似,就是寫法有點不同。

JSX 中的事件處理

這裏的不同主要包含如下兩點:

  • React 中的事件要使用駝峯式命名:onClick,而不是全小寫:onclick
  • 在 JSX 中,你傳遞的是一個事件處理函數,而不是一個字符串。

在 HTML 中,咱們處理事件是這樣的:

<button onclick="handleClick()">點我</button>
複製代碼

在 React 中,咱們須要寫成下面這樣:

function Button() {
  function handleClick() {
    console.log('按鈕被點擊了');
  }

  return (
    <button onClick={handleClick}>點我</button>
  )
}

複製代碼

注意到咱們在上面定義了一個函數式組件,而後返回一個按鈕,並在按鈕上面定義了點擊事件和對應的處理方法。

注意

這裏咱們的的點擊事件使用了駝峯式的 onClick 來命名,而且在 JSX 中傳給事件的屬性是一個函數:handleClick ,而不是以前 HTML 中單純的一個字符串:"handleClick()"

合成事件

咱們在之前編寫 HTML 的事件處理時,特別是在處理表單時,經常須要禁用瀏覽器的默認屬性。

提示

好比通常表單提交時都會刷新瀏覽器,可是咱們有時候但願提交表單以後不刷新瀏覽器,因此咱們須要禁用瀏覽器的默認屬性。

在 HTML 中咱們禁用事件的默認屬性是經過調用定義在事件上的 preventDefault 或者設置事件的 cancelBubble

// 當點擊某個連接以後,禁止打開頁面
document.getElementById("myAnchor").addEventListener("click", function(event){
  event.preventDefault()
});
複製代碼

在 JSX 中,事件處理和這個相似:

function Link() {
  function handleClick(event) {
    event.preventDefault();
    console.log('連接被點擊了,可是它不會跳轉頁面,由於默認行爲被禁用了');
  }

  return (
    <a onClick={handleClick} href="https://tuture.co">點我</a>
  )
}
複製代碼

實戰

咱們運用這一節中學到的知識來繼續完成咱們的待辦事項應用。

咱們以前的待辦事項的 todoList 數組都是直接硬編碼在代碼裏,不能夠進行增刪改,這至關死板,一個更真實的 todoList 應該要具有增長功能,這一功能實現須要兩個步驟:

  • 容許用戶輸入新的待辦事項。
  • 將這個輸入的待辦事項加入到現有的 todoList 列表裏面。

在這一小節中,咱們未來實現第一個步驟的內容。

打開 src/index.js ,對 App 組件內容做出以下修改:

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

    this.state = {
      nowTodo: "",
      todoList: []
    };
  }

  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        todoList: todoList
      });
    }, 2000);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

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

  render() {
    return (
      <div>
        <div>
          <input type="text" onChange={e => this.handleChange(e)} />
          <div>{this.state.nowTodo}</div>
        </div>
        <ul>
          {this.state.todoList.map((todo, index) => (
            <Todo content={todo} key={index} index={index} />
          ))}
        </ul>
      </div>
    );
  }
}
複製代碼

能夠看到,咱們新加入的代碼主要有四個部分:

  • 首先在 state 裏面添加了一個新的屬性 nowTodo,咱們將用它來保存用戶新輸入的待辦事項。
  • 而後咱們在 render 方法裏面,在返回的 JSX 中使用 div 將內容包裹起來,接着加入了一個 div,在裏面加入了一個 input 和 一個 div,input 用於處理用戶的輸入,咱們在上面定義了 onChange 事件,事件處理函數是一個箭頭函數,它接收事件 e,而後用戶輸入時,會在函數裏面調用 this.handleChange 方法,將事件 e 傳給這個方法。
  • handleChange 裏面經過 this.setState 使用 input 的值來更新 nowTodo 的內容。
  • 最後在 div 裏面使用 {} 插值語法展現 nowTodo 的內容。

保存代碼,打開瀏覽器,你應該能夠看到以下的內容:

當你嘗試在輸入框中鍵入內容時,輸入框的下面應會顯示相同的內容:

這是由於當咱們在輸入框裏面輸入內容時,咱們使用了輸入框的值更新 this.state.nowTodo,而後在輸入框之下展現 this.state.nowTodo 的值。

表單

接下來咱們來完成增長新的待辦事項的功能的第二個步驟:容許用戶將新輸入的待辦事項加入到 todoList 列表中。

打開 src/index.js 文件,對 App 組件作出以下修改:

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

    this.state = {
      nowTodo: "",
      todoList: []
    };
  }

  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        todoList: todoList
      });
    }, 2000);
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

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

  handleSubmit(e) {
    e.preventDefault(e);
    const newTodoList = this.state.todoList.concat(this.state.nowTodo);

    this.setState({
      todoList: newTodoList,
      nowTodo: ""
    });
  }

  render() {
    return (
      <div>
        <form onSubmit={e => this.handleSubmit(e)}>
          <input type="text" onChange={e => this.handleChange(e)} />
          <button type="submit">提交</button>
        </form>
        <ul>
          {this.state.todoList.map((todo, index) => (
            <Todo content={todo} key={index} index={index} />
          ))}
        </ul>
      </div>
    );
  }
}
複製代碼

上面的變化主要有三個部分:

  • 首先咱們將 render 方法中返回的 JSX 的最外層的 div 替換成了 form,而後在上面定義了 onSubmit 提交事件,而且經過一個箭頭函數接收事件 e 來進行事件處理,在 form 被提交時,在箭頭函數裏面會調用 handleSubmit 方法, 並將 e 傳遞給這個函數。
  • 在 handleSubmit 方法裏面,咱們首先調用了 e.preventDefault() 方法,來禁止瀏覽器的默認事件處理,這樣咱們在提交表單以後,瀏覽器就不會刷新,以後是將現有的 this.sate.todoList 列表加上新輸入的 nowTodo,最後是使用 this.setState 更新 todoListnowTodo;這樣咱們就能夠經過輸入內容添加新的待辦事項了。
  • 接着咱們將以前的展現 this.state.nowTodo 的 div 替換成 提交按鈕 button,並將 button 的 type 設置爲 submit 屬性,表示在點擊這個 button 以後,會觸發表單提交;將新輸入的內容加入現有的待辦事項中。

注意

咱們在 handleSubmit 方法裏面使用 this.setState 更新狀態時,將 nowTodo 設置爲了空字符串,表明咱們在加入新的待辦事項以後,將清除現有輸入的 nowTodo 待辦事項內容。

保存代碼,打開瀏覽器,在輸入框裏面輸入點東西,你應該能夠看到下面的內容:

當你點擊提交按鈕以後,新的待辦事項會加入到現有的 todoList 列表中,你應該能夠看到下面的內容:

恭喜你!你成功使用 React 完成了一個簡單的待辦事項應用,它能夠完成以下的功能:

  • 異步獲取將要展現的待辦事項:todoList
  • 將待辦事項展現出來
  • 偶數項待辦事項將會展現成紅色
  • 能夠添加新的待辦事項

作得好!咱們但願你如今已經對 React 的運行機制有了一個比較完整的瞭解,也但願本篇教程可以爲你踏入 React 開發世界提供一個好的開始!感謝你的閱讀!

後記

受限於篇幅,咱們的待辦事項還不完整,若是你有額外的時間或者你想要練習你新學到的 React 知識,下面是一些使咱們的待辦事項變得完整的一些想法,咱們將按實現難度給這些功能排序:

  • 在添加新的待辦事項以後,清空輸入框裏面的內容,方便下一次輸入。這樣涉及到 React 受控組件的知識。
  • 容許對單個事項進行刪除。這涉及到子組件修改父組件的狀態知識。
  • 容許用戶對單個事項進行修改。
  • 容許用戶對待辦事項進行搜索。

想要學習更多精彩的實戰技術教程?來圖雀社區逛逛吧。

相關文章
相關標籤/搜索