react學習

react學習javascript

react 一個UI框架

React用於構建用戶界面的 JavaScript 庫.css

react 特色

  • mvvm
  • 聲明式
  • 組件化
  • 生態強大

學習前置基礎

  • html\css\js
  • es6+
  • webpack教程
  • nodejs基礎|sass|ajax|...

簡單html的方式編寫react demo快速入門

咱們能夠直接把React的當作一個JS的庫來用(生產環境不要這麼用),以下是第一個helloword demohtml

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <!-- 第一步: 引入react的cdn的js庫 -->
  <!-- react的核心庫 -->
  <script crossorigin src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
  <!-- react渲染到瀏覽器上的支持庫(react能夠渲染到其餘的平臺) -->
  <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
  <!-- babel的轉換的js庫,生產環境不要使用 -->
  <script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
  <title>aicoder.com  reactdemos</title>
</head>
<body>

  <!-- 第二步:添加html容器 -->
  <!-- 這個是react的容器 -->
  <div id="app">

  </div>

  <!-- 第三步:添加babel的script腳本,這個是核心的React的核心 -->
  <!-- 注意:type必須是text/babel -->
  <script type="text/babel">
    ReactDOM.render(
      <h1>Hello, world! aicoder.com </h1>,
      document.getElementById('app')
    );
  </script>
</body>
</html>

 

ReactDOM.render方法是把JSX語法生成的dom渲染到頁面上去。此方法接受兩個參數,第一個參數是渲染的html標籤,第二個參數是渲染到頁面的哪一個節點上。前端

這裏牽扯到JSX語法,後續會講到。java

React腳手架建立項目快速入門

快速構建一個React的前端項目最好的就是用腳手架快速生成一個項目架構目錄,並作好基礎的配置。建議使用Create React Appnode

安裝Create React App

# 建議全局安裝
$ npm install -g create-react-app

 

# yarn
$ yarn global add create-react-app

 

 

測試是否安裝成功:react

$ create-react-app -V
2.1.3

 

快速初始化一個react項目

npx create-react-app myapp
cd myapp
npm start

 

 

此時打開http://localhost:3000/就能看到基本的一個簡單的web頁面。webpack

釋放webpack的配置文件

因爲create-react-app腳手架生成的項目全部的配置都內置在代碼中,咱們看不到webpack配置的細節,須要經過一個命令,把全部配置都顯示的展示在項目中。ios

npm run eject

除非您對webpack已經很是熟悉,請不要這麼操做!git

其餘的構建輔助腳本

# 構建項目
npm run build

yarn build

# 運行測試
npm run test
yarn test

# 另一種構建方式
# required npm 6.0+
npm init react-app my-app

yarn create react-app my-app

 

 

HelloWorld

項目的默認目錄:

├── package.json
├── public                  # 這個是webpack的配置的靜態目錄
│   ├── favicon.ico
│   ├── index.html          # 默認是單頁面應用,這個是最終的html的基礎模板
│   └── manifest.json
├── src
│   ├── App.css             # App根組件的css
│   ├── App.js              # App組件代碼
│   ├── App.test.js
│   ├── index.css           # 啓動文件樣式
│   ├── index.js            # 啓動的文件(開始執行的入口)!!!!
│   ├── logo.svg
│   └── serviceWorker.js
└── yarn.lock

index.js就是項目啓動啓動的入口。

import React from 'react';                           // 引入react核心庫(必須)
import ReactDOM from 'react-dom';                    // 引入react在瀏覽器上運行的須要的支持庫
import './index.css';
import App from './App';                             // 引入App組件
import * as serviceWorker from './serviceWorker';    // 註冊serviceWork

// 此行代碼的意思:把App組件的內容通過Ract的編譯生成最終的html掛載到 root的dom節點生。
ReactDOM.render(<App />, document.getElementById('root'));  // !!!核心代碼

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

 

核心代碼就是下面一行:把App組件的內容通過Ract的編譯生成最終的html掛載到 root的dom節點生。

ReactDOM.render(<App />, document.getElementById('root')); // !!!核心代碼

 

那麼咱們看一下App組件的代碼:

import React, { Component } from 'react';   // 引入react的組件根對象
import logo from './logo.svg';
import './App.css';

class App extends Component {              // 建立App組件類型,繼承Component
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p>
            Edit <code>src/App.js</code> and save to reload.
          </p>
          <a
            className="App-link"
            href="https://reactjs.org"
            target="_blank"
            rel="noopener noreferrer"
          >
            Learn React
          </a>
        </header>
      </div>
    );
  }
}
export default App;

 

您須要有es6的語法的基礎。

在App.js中就作了如下幾件事:

  • 引入React庫
  • 定義App類型(繼承自React.Component)
  • 在App類中定義render方法
  • 在render方法中返回要渲染的html(jsx語法)

而後咱們修改以下App.js爲:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <h1>Hi, aicoder.com</h1>
      </div>
    );
  }
}

export default App;

 

此時頁面會自動刷新爲:

Hi, aicoder.com

JSX語法

JSX, 一種 JavaScript 的語法擴展。JSX 用來聲明 React 當中的元素。

好比定義一個變量:

// jsx語法是js和html的組合,本質仍是js,最終會編譯成js
const element = <h1>Hello, world!</h1>;

 

JSX 的基本語法規則:遇到 HTML 標籤(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。

JSX中使用表達式

若是JSX中的代碼超過一行,咱們通常用一個()進行分組處理,遇到html通常都會單獨寫在一個新行。

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

 

再好比:

// 用{}能夠直接展現數據內容個,相似es6模板字符串中的 ${}
function getGreeting(user) {
  if (user) {
    return <h1>Hello, {user}!</h1>;
  }
  return <h1>Hello, Stranger.</h1>;
}

 

JSX 屬性與{}

你可使用引號來定義以字符串爲值的屬性:

const element = <div tabIndex="0"></div>;

 

也可使用大括號來定義以 JavaScript 表達式爲值的屬性:

const element = <img src={user.avatarUrl} />;

 

JSX 防注入攻擊

你能夠放心地在 JSX 當中使用用戶輸入:

const title = <span>你好!</span>;
// 直接使用是安全的:
const element = <h1>{title}</h1>;

 

React DOM 在渲染以前默認會 過濾 全部傳入的值。它能夠確保你的應用不會被注入攻擊。全部的內容在渲染以前都被轉換成了字符串。這樣能夠有效地防止 XSS(跨站腳本) 攻擊。

數組的展現

變量是一個數組,則會展開這個數組的全部成員。

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    var arr = [
      <h1>hi, aicoder.com</h1>,
      <h2>React is awesome</h2>,
    ];
    return (
      <div className="App">
        {arr}
      </div>
    );
  }
}
export default App;

 

最終結果:

hi, aicoder.com
React is awesome

數組map輸出一個列表

App.js以下:

import React, { Component } from 'react';
import './App.css';

class App extends Component {
  render() {
    var arr = ['aicoder.com', 'hamkd.com']
    return (
      <div className="App">
        <h1>aicoder.com</h1>
        <ul>
          {
            arr.map((item, index) =>
              <li>{ index +1 } - { item }</li>
            )
          }
        </ul>
      </div>
    );
  }
}
export default App;

 

最終結果:

aicoder.com
1 - aicoder.com
2 - hamkd.com

JSX的最終歸宿

JSX 本質會被編譯成JS,Babel 轉譯器會把 JSX 轉換成一個名爲 React.createElement() 的方法調用。

下面兩種代碼的做用是徹底相同的:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

 

React.createElement() 這個方法首先會進行一些避免bug的檢查,以後會返回一個相似下面例子的對象:
// 注意: 如下示例是簡化過的(不表明在 React 源碼中是這樣)
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
};
 

這樣的對象被稱爲 「React 元素」。它表明全部你在屏幕上看到的東西。React 經過讀取這些對象來構建 DOM 並保持數據內容一致。

React組件

組件能夠將UI切分紅一些獨立的、可複用的部件,這樣你就只需專一於構建每個單獨的部件。

組件從概念上看就像是函數,它能夠接收任意的輸入值(稱之爲「props」),並返回一個須要在頁面上展現的React元素。

函數定義/類定義組件

定義一個組件最簡單的方式是使用JavaScript函數:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

 

該函數是一個有效的React組件,它接收一個單一的「props」對象並返回了一個React元素。咱們之因此稱這種類型的組件爲函數定義組件,是由於從字面上來看,它就是一個JavaScript函數。

你也可使用 ES6 class 來定義一個組件:

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

 

上面兩個組件在React中是相同的。

組件渲染

在前面,咱們遇到的React元素都只是DOM標籤:

const element = <div />;

 

然而,React元素也能夠是用戶自定義的組件:

const element = <Welcome name="Sara" />;

 

當React遇到的元素是用戶自定義的組件,它會將JSX屬性做爲單個對象傳遞給該組件,這個對象稱之爲「props」。

例如,這段代碼會在頁面上渲染出"Hello,Sara":

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// ...
class App extends Component {
  render() {
    return (
      <div className="App">
        <Welcome name="Sara" />
      </div>
    );
  }
}
// ...

 

咱們來回顧一下在這個例子中發生了什麼:

  1. 咱們對<Welcome name="Sara" />元素調用了ReactDOM.render()方法。
  2. React將{name: 'Sara'}做爲props傳入並調用Welcome組件。
  3. Welcome組件將<h1>Hello, Sara</h1>元素做爲結果返回。
  4. 將DOM更新爲<h1>Hello, Sara</h1>

警告:

組件名稱必須以大寫字母開頭。

例如,<div /> 表示一個DOM標籤,但 <Welcome /> 表示一個組件,而且在使用該組件時你必須定義或引入它。

組合組件

組件能夠在它的輸出中引用其它組件,這就可讓咱們用同一組件來抽象出任意層次的細節。在React應用中,按鈕、表單、對話框、整個屏幕的內容等,這些一般都被表示爲組件。

例如,咱們能夠建立一個App組件,用來屢次渲染Welcome組件:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

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

 

一般,一個新的React應用程序的頂部是一個App組件。可是,若是要將React集成到現有應用程序中,則能夠從下而上使用像Button這樣的小組件做爲開始,並逐漸運用到視圖層的頂部。

警告:

組件的返回值只能有一個根元素。這也是咱們要用一個<div>來包裹全部<Welcome />元素的緣由。

提取組件

你能夠將組件切分爲更小的組件,這沒什麼好擔憂的。

例如,來看看這個Comment組件:

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

 

這個組件接收author(對象)、text(字符串)、以及date(Date對象)做爲props,用來描述一個社交媒體網站上的評論。

這個組件因爲嵌套,變得難以被修改,可複用的部分也難以被複用。因此讓咱們從這個組件中提取出一些小組件。

首先,咱們來提取Avatar組件:

function Avatar(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

 

Avatar做爲Comment的內部組件,不須要知道是否被渲染。所以咱們將author改成一個更通用的名字user

咱們建議從組件自身的角度來命名props,而不是根據使用組件的上下文命名。

如今咱們能夠對Comment組件作一些小小的調整:

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <Avatar user={props.author} />
        <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>
  );
}

 

提取組件一開始看起來像是一項單調乏味的工做,可是在大型應用中,構建可複用的組件徹底是值得的。當你的UI中有一部分重複使用了好幾回(好比,ButtonPanelAvatar),或者其自身就足夠複雜(好比,AppFeedStoryComment),相似這些都是抽象成一個可複用組件的絕佳選擇,這也是一個比較好的作法。

Props的只讀性

不管是使用函數或是類來聲明一個組件,它決不能修改它本身的props。來看這個sum函數:

function sum(a, b) {
  return a + b;
}

 

相似於上面的這種函數稱爲「純函數」,它沒有改變它本身的輸入值,當傳入的值相同時,老是會返回相同的結果。

與之相對的是非純函數,它會改變它自身的輸入值:

function withdraw(account, amount) {
  account.total -= amount;
}

 

React是很是靈活的,但它也有一個嚴格的規則:

全部的React組件必須像純函數那樣使用它們的props。

固然,應用的界面是隨時間動態變化的,後面會介紹一種稱爲「state」的新概念,State能夠在不違反上述規則的狀況下,根據用戶操做、網絡響應、或者其餘狀態變化,使組件動態的響應並改變組件的輸出。

組件的生命週期

React組件的生命週期圖解

React 生命週期圖解

組件會隨着組件的props和state改變而發生變化,它的DOM也會有相應的變化。一個組件就是一個狀態機:對於特定的輸入,它總會返回一致的輸出。

React組件提供了生命週期的鉤子函數去響應組件不一樣時刻的狀態,組件的生命週期以下:

  • 實例化階段
  • 存在期階段
  • 銷燬期階段

實例化階段

首次調用組件時,實例化階段有如下方法會被調用(注意順序,從上到下前後執行):

getDefaultProps

這個方法是用來設置組件默認的props,組件生命週期只會調用一次。可是隻適合React.createClass直接建立的組件,使用ES6/ES7建立的這個方法不可以使用,ES6/ES7可使用下面方式:

//es7
class Component {
  static defaultProps = {}
}

 

getInitialState

設置state初始值,在這個方法中你已經能夠訪問到this.props。getDefaultProps只適合React.createClass使用。使用ES6初始化state方法以下:

class Component extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      render: true,
    }
  }
}
// 或者這樣, es6 stage3

class Component extends React.Component{
  state = {
    render: true
  }
  render(){return false;}
}

 

componentWillMount

此方法會在組件首次渲染以前調用,這個是在render方法調用前可修改state的最後一次機會。這個方法不多用到。

render

這個方法之後你們都應該會很熟悉,JSX經過這裏,解析成對應的虛擬DOM,渲染成最終效果。格式大體以下:

class Component extends React.Component{
  render(){
    return (
       <div></div>
    )
  }
}

 

componentDidMount

這個方法在首次真實的DOM渲染後調用(僅此一次)當咱們須要訪問真實的DOM時,這個方法就常常用到。如何訪問真實的DOM這裏就不想說了。當咱們須要請求外部接口數據,通常都在這裏處理。

存在期

實例化後,當props或者state發生變化時,下面方法依次被調用:

componentWillReceiveProps

沒當咱們經過父組件更新子組件props時(這個也是惟一途徑),這個方法就會被調用。

componentWillReceiveProps(nextProps){}

shouldComponentUpdate

字面意思,是否應該更新組件,默認返回true。當返回false時,後期函數就不會調用,組件不會在次渲染。

shouldComponentUpdate(nextProps,nextState){}

componentWillUpdate

字面意思組件將會更新,props和state改變後必調用。

render

跟實例化時的render同樣,很少說

componentDidUpdate

這個方法在更新真實的DOM成功後調用,當咱們須要訪問真實的DOM時,這個方法就也常常用到。

銷燬期

銷燬階段,只有一個函數被調用:

componentWillUnmount

沒當組件使用完成,這個組件就必須從DOM中銷燬,此時該方法就會被調用。當咱們在組件中使用了setInterval,那咱們就須要在這個方法中調用clearTimeout。

參考:React組件生命週期

React組件的狀態state

咱們能夠經過this.props獲取一個組件的屬性,另外還能夠經過this.state獲取組件的私有狀態(也就是私有數據)。

構造函數初始化內部的狀態

import React, { Component } from 'react'

class Clock extends Component {
  constructor(props) {
    super(props);
    // 構造函數中設置狀態
    this.state = {
      DateStr: new Date().toLocaleDateString()
    };
  }

  render () {
    return (
      <div>
        <p>當前時間是: {this.state.DateStr}</p>
      </div>
    )
  }
}

export default Clock

 

關於 setState() 這裏有三件事情須要知道

不要直接更新狀態

例如,此代碼不會從新渲染組件:

// Wrong this.state.comment = 'Hello';

應當使用 setState():

// Correct
this.setState({comment: 'Hello'});

 

構造函數是惟一可以初始化 this.state 的地方。

狀態更新多是異步的

React 能夠將多個setState() 調用合併成一個調用來提升性能。

由於 this.props 和 this.state 多是異步更新的,你不該該依靠它們的值來計算下一個狀態。

例如,此代碼可能沒法更新計數器:

// Wrong this.setState({ counter: this.state.counter + this.props.increment, });

要修復它,請使用第二種形式的 setState() 來接受一個函數而不是一個對象。 該函數將接收先前的狀態做爲第一個參數,將這次更新被應用時的props作爲第二個參數:

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

 

上方代碼使用了箭頭函數,但它也適用於常規函數:

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

 

狀態單獨更新和自動合併

當你調用 setState() 時,React 將你提供的對象合併到當前狀態。

例如,你的狀態可能包含一些獨立的變量:

constructor(props) { super(props); this.state = { posts: [], comments: [] }; }

你能夠調用 setState() 獨立地更新它們:

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

    fetchComments().then(response => {
      this.setState({
        comments: response.comments
      });
    });
  }

 

這裏的合併是淺合併,也就是說this.setState({comments})完整保留了this.state.posts,但徹底替換了this.state.comments

數據自頂向下流動

父組件或子組件都不能知道某個組件是有狀態仍是無狀態,而且它們不該該關心某組件是被定義爲一個函數仍是一個類。

這就是爲何狀態一般被稱爲局部或封裝。 除了擁有並設置它的組件外,其它組件不可訪問。

組件能夠選擇將其狀態做爲屬性傳遞給其子組件:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

 

這也適用於用戶定義的組件:

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

 

FormattedDate 組件將在其屬性中接收到 date 值,而且不知道它是來自 Clock 狀態、仍是來自 Clock 的屬性、亦或手工輸入:

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

 

這一般被稱爲自頂向下單向數據流。 任何狀態始終由某些特定組件全部,而且從該狀態導出的任何數據或 UI 只能影響樹中下方的組件。

若是你想象一個組件樹做爲屬性的瀑布,每一個組件的狀態就像一個額外的水源,它鏈接在一個任意點,但也流下來。

爲了代表全部組件都是真正隔離的,咱們能夠建立一個 App 組件,它渲染三個Clock

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

 

每一個 Clock 創建本身的定時器而且獨立更新。 在 React 應用程序中,組件是有狀態仍是無狀態被認爲是可能隨時間而變化的組件的實現細節。 能夠在有狀態組件中使用無狀態組件,反之亦然。

綜合案例自動計時的時鐘

import React, { Component } from 'react'

class Clock extends Component {
  constructor(props) {
    super(props);
    this.state = {
      DateStr: new Date().toLocaleTimeString(),
      timer: null
    };
  }
  componentDidMount() {
    this.setState({
      timer: setInterval(() => {
        this.setState({
          DateStr: new Date().toLocaleTimeString()
        });
      }, 1000)
    });
  }

  componentWillUnmount() {
    clearInterval(this.state.timer);
  }

  render () {
    return (
      <div>
        <p>當前時間是: {this.state.DateStr}</p>
      </div>
    )
  }
}

export default Clock

 

React的事件處理

React 元素的事件處理和 DOM元素的很類似。可是有一點語法上的不一樣:

  • React事件綁定屬性的命名採用駝峯式寫法,而不是小寫。
  • 若是採用 JSX 的語法你須要傳入一個函數做爲事件處理函數,而不是一個字符串(DOM元素的寫法)

JSX語法中使用{}執行js代碼。

例如,傳統的 HTML:

<button onclick="activateLasers()"> Activate Lasers </button>

React 中稍稍有點不一樣:

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

 

在 React 中另外一個不一樣是你不能使用返回 false 的方式阻止默認行爲。你必須明確的使用 preventDefault。例如,傳統的 HTML 中阻止連接默認打開一個新頁面,你能夠這樣寫:

<a href="#" onclick="console.log('The link was clicked.'); return false"> Click me </a>

在 React,應該這樣來寫:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('The link was clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>
      Click me
    </a>
  );
}

 

在這裏,e 是一個合成事件。React 根據 W3C spec 來定義這些合成事件,因此你不須要擔憂跨瀏覽器的兼容性問題。

使用 React 的時候一般你不須要使用 addEventListener 爲一個已建立的 DOM 元素添加監聽器。你僅僅須要在這個元素初始渲染的時候提供一個監聽器。

當你使用 ES6 class 語法來定義一個組件的時候,事件處理器會成爲類的一個方法。例如,下面的 Toggle 組件渲染一個讓用戶切換開關狀態的按鈕:

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

    // This binding is necessary to make `this` work in the 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')
);

 

你必須謹慎對待 JSX 回調函數中的 this,類的方法默認是不會綁定 this 的。若是你忘記綁定 this.handleClick 並把它傳入 onClick, 當你調用這個函數的時候 this 的值會是 undefined

這並非 React 的特殊行爲;它是函數如何在 JavaScript 中運行的一部分。一般狀況下,若是你沒有在方法後面添加 () ,例如 onClick={this.handleClick},你應該爲這個方法綁定 this

若是使用 bind 讓你很煩,這裏有兩種方式能夠解決。若是你正在使用實驗性的屬性初始化器語法,你可使用屬性初始化器來正確的綁定回調函數:

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: 最新的es的語法,暫時是在stage3
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

 

這個語法在 Create React App 中默認開啓。

若是你沒有使用屬性初始化器語法,你能夠在回調函數中使用 箭頭函數

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // This syntax ensures `this` is bound within handleClick
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

 

使用這個語法有個問題就是每次 LoggingButton 渲染的時候都會建立一個不一樣的回調函數。在大多數狀況下,這沒有問題。然而若是這個回調函數做爲一個屬性值傳入低階組件,這些組件可能會進行額外的從新渲染。咱們一般建議在構造函數中綁定或使用屬性初始化器語法來避免這類性能問題。

demo

import React, { Component } from 'react';
class App extends Component {

  constructor(option) {
    super(option);
    this.handler = this.handler.bind(this);
  }

  handler(e) {
    console.log(e);
    console.log(this);
  }

  hanlderClick = (e) => {
     console.log(e);
     e.target.innerText = Date.now();
  };
  handlerButtonClick(e) {
    console.log('button click');
  }

  render() {
    return (
      <div className="App">
        <h1>aicoder.com</h1>
        <button onClick={this.hanlderClick}>類對象實例屬性箭頭函數</button>
        <button onClick={this.handler}>構造函數綁定this</button>
        <button onClick={this.handlerButtonClick.bind(this)}>直接bind的this</button>
        <p onMouseEnter={(e) => {console.log(this); console.log(3);}}>nihao</p>
      </div>
    );
  }
}

export default App;

 

向事件處理程序傳遞參數

一般咱們會爲事件處理程序傳遞額外的參數。例如,如果 id 是你要刪除那一行的 id,如下兩種方式均可以向事件處理程序傳遞參數:

<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

 

上述兩種方式是等價的,分別經過 arrow functions 和 Function.prototype.bind 來爲事件處理函數傳遞參數。

上面兩個例子中,參數 e 做爲 React 事件對象將會被做爲第二個參數進行傳遞。經過箭頭函數的方式,事件對象必須顯式的進行傳遞,可是經過 bind 的方式,事件對象以及更多的參數將會被隱式的進行傳遞。

值得注意的是,經過 bind 方式向監聽函數傳參,在類組件中定義的監聽函數,事件對象 e 要排在所傳遞參數的後面,例如:

class Popper extends React.Component{
    constructor(){
        super();
        this.state = {name:'Hello world!'};
    }
    preventPop(name, e){    //事件對象e要放在最後
        e.preventDefault();
        alert(name);
    }
    render(){
        return (
            <div>
                <p>hello</p>
                {/* Pass params via bind() method. */}
                <a href="https://reactjs.org" onClick={this.preventPop.bind(this,this.state.name)}>Click</a>
            </div>
        );
    }
}

 

條件渲染

在 React 中,你能夠建立不一樣的組件來封裝各類你須要的行爲。而後還能夠根據應用的狀態變化只渲染其中的一部分。

React 中的條件渲染和 JavaScript 中的一致,使用 JavaScript 操做符 if 或條件運算符來建立表示當前狀態的元素,而後讓 React 根據它們來更新 UI。

先來看兩個組件:

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

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

 

咱們將建立一個 Greeting 組件,它會根據用戶是否登陸來顯示其中之一:

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

ReactDOM.render(
  // Try changing to isLoggedIn={true}:
  <Greeting isLoggedIn={false} />,
  document.getElementById('root')
);

 

 

此示例根據 isLoggedIn 的值渲染不一樣的問候語。

元素變量

你可使用變量來儲存元素。它能夠幫助你有條件的渲染組件的一部分,而輸出的其餘部分不會更改。

再來看兩個新組件分別表明註銷和登陸:

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

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

 

在下面的例子中,咱們將要建立一個名爲 LoginControl 的有狀態的組件

它會根據當前的狀態來渲染 <LoginButton /> 或 <LogoutButton />,它也將渲染前面例子中的 <Greeting />

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

 

 

聲明變量並使用 if 語句是條件渲染組件的不錯的方式,但有時你也想使用更簡潔的語法,在 JSX 中有以下幾種方法。

與運算符 &&

你能夠經過用花括號包裹代碼在 JSX 中嵌入任何表達式 ,也包括 JavaScript 的邏輯與 &&,它能夠方便地條件渲染一個元素。

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&& 右側的元素就會被渲染,若是是 false,React 會忽略並跳過它。

三目運算符

條件渲染的另外一種方法是使用 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>
  );
}

 

像在 JavaScript 中同樣,你能夠根據團隊的習慣選擇更易讀的方式。還要記住若是條件變得過於複雜,可能就是提取組件的好時機了。

阻止組件渲染

在極少數狀況下,你可能但願隱藏組件,即便它被其餘組件渲染。讓 render 方法返回 null 而不是它的渲染結果便可實現。

在下面的例子中,<WarningBanner /> 根據屬性 warn 的值條件渲染。若是 warn 的值是 false,則組件不會渲染:

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(prevState => ({
      showWarning: !prevState.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')
);

 

 

組件的 render 方法返回 null 並不會影響該組件生命週期方法的回調。例如,componentWillUpdate 和 componentDidUpdate 依然能夠被調用。

React列表 & Keys

首先,讓咱們看下在Javascript中如何轉化列表

以下代碼,咱們使用map()函數讓數組中的每一項翻倍,咱們獲得了一個新的數列doubled

const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);
console.log(doubled);

 

代碼打印出[2, 4, 6, 8, 10]

在React中,把數組轉化爲數列元素的過程是類似的

渲染多個組件

你能夠經過使用{}在JSX內構建一個元素集合

下面,咱們使用Javascript中的map()方法遍歷numbers數組。對數組中的每一個元素返回<li>標籤,最後咱們獲得一個數組listItems

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

 

咱們把整個listItems插入到ul元素中,而後渲染進DOM:

ReactDOM.render(
  <ul>{listItems}</ul>,
  document.getElementById('root')
);

 

 

這段代碼生成了一個1到5的數字列表

基礎列表組件

一般你須要渲染一個列表到組件

咱們能夠把前面的例子重構成一個組件。這個組件接收numbers數組做爲參數,輸出一個無序列表。

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

 

當咱們運行這段代碼,將會看到一個警告 a key should be provided for list items ,意思是當你建立一個元素時,必須包括一個特殊的 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')
);

 

 

Keys

Keys能夠在DOM中的某些元素被增長或刪除的時候幫助React識別哪些元素髮生了變化。所以你應當給數組中的每個元素賦予一個肯定的標識。

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

 

一個元素的key最好是這個元素在列表中擁有的一個獨一無二的字符串。一般,咱們使用來自數據的id做爲元素的key:

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

 

當元素沒有肯定的id時,你可使用他的序列號索引index做爲key

const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);

 

若是列表項目的順序可能會變化,咱們不建議使用索引來用做鍵值,由於這樣作會致使性能的負面影響,還可能引發組件狀態問題。若是你想要了解更多,請點擊深度解析key的必要性。若是你選擇不指定顯式的鍵值,那麼React將默認使用索引用做爲列表項目的鍵值。

這裏有一篇文章 in-depth explanation about why keys are necessary ,要是你有興趣瞭解更多的話。

用keys提取組件

元素的key只有放在其環繞數組的上下文中才有意義。

比方說,若是你提取出一個ListItem組件,你應該把key保存在數組中的這個<ListItem />元素上,而不是放在ListItem組件中的<li>元素上。

function ListItem(props) {
  // 對啦!這裏不須要指定key:
  return <li>{props.value}</li>;
}

function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    // 又對啦!key應該在數組的上下文中被指定
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      {listItems}
    </ul>
  );
}

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

 

 

一個好的大拇指法則:元素位於map()方法內時須要設置鍵屬性。

鍵(key)只是在兄弟之間必須惟一

數組元素中使用的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的提示,但不會傳遞給你的組件。若是您的組件中須要使用和key相同的值,請用其餘屬性名顯式傳遞這個值:

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

 

上面例子中,Post組件能夠讀出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容許在大括號中嵌入任何表達式,因此咱們能夠在map()中這樣使用:

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

 

 

這麼作有時可使你的代碼更清晰,但有時這種風格也會被濫用。就像在JavaScript中同樣,什麼時候須要爲了可讀性提取出一個變量,這徹底取決於你。但請記住,若是一個map()嵌套了太多層級,那可能就是你提取出組件的一個好時機。

表單

HTML表單元素與React中的其餘DOM元素有所不一樣,由於表單元素生來就保留一些內部狀態。例如,下面這個表單只接受一個惟一的name。

<form>
  <label>
    Name:
    <input type="text" name="name" />
  </label>
  <input type="submit" value="Submit" />
</form>

 

當用戶提交表單時,HTML的默認行爲會使這個表單跳轉到一個新頁面。在React中亦是如此。但大多數狀況下,咱們都會構造一個處理提交表單並可訪問用戶輸入表單數據的函數。實現這一點的標準方法是使用一種稱爲「受控組件」的技術。

受控組件

在HTML當中,像<input>,<textarea>, 和 <select>這類表單元素會維持自身狀態,並根據用戶輸入進行更新。但在React中,可變的狀態一般保存在組件的狀態屬性中,而且只能用 setState()方法進行更新。

咱們經過使react變成一種單一數據源的狀態來結合兩者。React負責渲染表單的組件仍然控制用戶後續輸入時所發生的變化。相應的,其值由React控制的輸入表單元素稱爲「受控組件」。

例如,咱們想要使上個例子中在提交表單時輸出name,咱們能夠寫成「受控組件」的形式:

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

 

在 CodePen 上嘗試。

因爲 value 屬性是在咱們的表單元素上設置的,所以顯示的值將始終爲 React數據源上this.state.value 的值。因爲每次按鍵都會觸發 handleChange 來更新當前React的state,所展現的值也會隨着不一樣用戶的輸入而更新。

使用"受控組件",每一個狀態的改變都有一個與之相關的處理函數。這樣就能夠直接修改或驗證用戶輸入。例如,咱們若是想限制輸入所有是大寫字母,咱們能夠將handleChange 寫爲以下:

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

 

textarea 標籤

在HTML當中,<textarea> 元素經過子節點來定義它的文本內容

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

 

在React中,<textarea>會用value屬性來代替。這樣的話,表單中的<textarea> 很是相似於使用單行輸入的表單:

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>
          Name:
          <textarea value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

 

注意this.state.value是在構造函數中初始化,這樣文本區域就能獲取到其中的文本。

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>

 

請注意,Coconut選項最初因爲selected屬性是被選中的。在React中,並不使用以前的selected屬性,而在根select標籤上用value屬性來表示選中項。這在受控組件中更爲方便,由於你只須要在一個地方來更新組件。例如:

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 La Croix 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>
    );
  }
}

 

在 CodePen 上嘗試。

總之,<input type="text"><textarea>, 和 <select> 都十分相似 - 他們都經過傳入一個value屬性來實現對組件的控制。

file input 標籤

在HTML當中,<input type="file"> 容許用戶從他們的存儲設備中選擇一個或多個文件以提交表單的方式上傳到服務器上, 或者經過 Javascript 的 File API 對文件進行操做 。

<input type="file" />

因爲該標籤的 value 屬性是隻讀的, 因此它是 React 中的一個非受控組件。咱們會把它和其餘非受控組件一塊兒在後面的章節進行詳細的介紹。

多個輸入的解決方法

當你有處理多個受控的input元素時,你能夠經過給每一個元素添加一個name屬性,來讓處理函數根據 event.target.name的值來選擇作什麼。

例如:

class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}

 

在 CodePen 上嘗試。

注意咱們如何使用ES6當中的計算屬性名語法來更新與給定輸入名稱相對應的狀態鍵:

this.setState({
  [name]: value
});

 

至關於以下ES5語法

var partialState = {};
partialState[name] = value;
this.setState(partialState);

 

一樣因爲 setState() 自動將部分狀態合併到當前狀態,所以咱們只須要使用發生變化的部分調用它。

受控組件的替代方法

有時使用受控組件可能很繁瑣,由於您要爲數據可能發生變化的每一種方式都編寫一個事件處理程序,並經過一個組件來管理所有的狀態。當您將預先存在的代碼庫轉換爲React或將React應用程序與非React庫集成時,這可能變得特別煩人。在以上狀況下,你或許應該看看非受控組件,這是一種表單的替代技術。

綜合自定義表單校驗案例

import React, { Component } from 'react';

class FormSub extends Component {
  constructor(opt) {
    super(opt);
    this.state = {
      Title: 'hi',
      Validate: {
        Title: {
          required: true,
          minLen: 6,
          maxLen: 10,
          validate: true,
          msg: '*ToDo不能爲空!'
        }
      }
    }
  }

  handlerChange = (e) => {
    // 設置狀態:是異步執行。
    this.setState({
      [e.target.name]: e.target.value
    }, () => {
      this.validateInput();
    });
  }

  handlerSubmit = (e) => {
    e.preventDefault();
    // 第一: 作表單的校驗
    this.validateInput();
    // 第二: 作表單提交到後臺ajax請求
  };

  validateInput() {
    let { Title, Validate } = this.state;
    let tempValidate = false;
    const len = Title.length;
    const min = Validate.Title.minLen;
    const max = Validate.Title.maxLen;
    if(len >= min && len <= max) {
      tempValidate = true;
    }

    this.setState(preState => {
      return Object.assign({}, preState, {
        Validate: {
          Title: Object.assign({}, preState.Validate.Title,{
            validate: tempValidate,
          })
        }
      });
    })
  }

  render() {
    return (
      <form onSubmit={this.handlerSubmit}>
        <label>
          ToDo:
          <input 
            type="text"
            name="Title"
            onChange={this.handlerChange}
            value={this.state.Title}
          />
          {
            !this.state.Validate.Title.validate &&
            <span 
              style={{color: 'red'}}
            >
              {this.state.Validate.Title.msg}
            </span>
          }
        </label>
        <br/>
        <input type="submit" value="提交"/>
      </form>
    );
  }
}

export default FormSub;

 

狀態提高

使用 react 常常會遇到幾個組件須要共用狀態數據的狀況。這種狀況下,咱們最好將這部分共享的狀態提高至他們最近的父組件當中進行管理。咱們來看一下具體如何操做吧

咱們一個計數的父組件,兩個按鈕組件,兩個按鈕組件分別對父組件中的數據進行添加和減小操做。

// Counter.js 父組件
import React, { Component } from 'react';

import ButtonAdd from './ButtonAdd';
import ButtonMinus from './ButtonMinus';

class Counter extends Component {
  constructor(option) {
    super(option);
    this.state = { num: 0, age: 19 };
  }
  minusCount(num, e) {
    this.setState((preState) => {
      return { num: preState.num - num }
    });
  }
  addCount(num, e) {
    this.setState((preState) => {
      return { num: preState.num + num }
    });
  }
  render() {
    return (
      <div>
        <p>parent: { this.state.num } -{ this.state.age }</p>
        <hr />
        <ButtonAdd addCount={ this.addCount.bind(this) } num={ this.state.num } />
        <ButtonMinus minusCount={ this.minusCount.bind(this) } num={ this.state.num }  />
      </div>
    );
  }
}

export default Counter;


// 子組件 添加按鈕組件
import React, { Component } from 'react';

class ButtonAdd extends Component {
  render() {
    return (
      <div>
        <span>child:state {this.props.num}</span>
        <button onClick={ () => {
          this.props.addCount(1);
        }}>
          +1
        </button>
      </div>
    );
  }
}

export default ButtonAdd;


// 子組件:  減小按鈕組件
import React, { Component } from 'react';

class ButtonMinus extends Component {
  render() {
    return (
      <div>
        <span>child:state { this.props.num }</span>
        <button onClick={ () => {
          this.props.minusCount(1);
        }}>
          -1
        </button>
      </div>
    );
  }
}

export default ButtonMinus;

 

組合與props.children

React 具備強大的組合模型,咱們建議使用組合而不是繼承來複用組件之間的代碼。

在本節中,咱們將圍繞幾個 React 新手常用繼承解決的問題,咱們將展現如何用組合來解決它們。

包含關係

一些組件不能提早知道它們的子組件是什麼。這對於 Sidebar 或 Dialog 這類通用容器尤爲常見。

咱們建議這些組件使用 children 屬性將子元素直接傳遞到輸出。

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

 

這樣作還容許其餘組件經過嵌套 JSX 來傳遞子組件。

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

 

 

<FancyBorder> JSX 標籤內的任何內容都將經過 children 屬性傳入 FancyBorder。因爲 FancyBorder 在一個 <div> 內渲染了 {props.children},因此被傳遞的全部元素都會出如今最終輸出中。

雖然不太常見,但有時你可能須要在組件中有多個入口,這種狀況下你可使用本身約定的屬性而不是 children

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

 

 

相似 <Contacts /> 和 <Chat /> 這樣的 React 元素都是對象,因此你能夠像任何其餘元素同樣傳遞它們。

特殊實例

有時咱們認爲組件是其餘組件的特殊實例。例如,咱們會說 WelcomeDialog 是 Dialog 的特殊實例。

在 React 中,這也是經過組合來實現的,經過配置屬性用較特殊的組件來渲染較通用的組件。

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

 

 

組合對於定義爲類的組件一樣適用:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

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

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

 

 

JSX高級

本質上來說,JSX 只是爲 React.createElement(component, props, ...children) 方法提供的語法糖。好比下面的代碼:

<MyButton color="blue" shadowSize=>
  Click Me
</MyButton>

 

編譯爲:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

 

若是沒有子代,你還可使用自閉合標籤,好比:

<div className="sidebar" />

 

編譯爲:

React.createElement(
  'div',
  {className: 'sidebar'},
  null
)

 

若是你想完全驗證 JSX 是如何轉換爲 JavaScript 的,你能夠嘗試 在線 Babel 編譯器.

指定 React 元素類型

JSX 的標籤的第一部分決定了 React 元素的類型。

首字母大寫的類型表示 JSX 標籤引用到一個 React 組件。這些標籤將會被編譯爲直接引用同名變量,因此若是你使用了 <Foo /> JSX 表達式,則 Foo 必須在做用域中。

React 必須在做用域中

因爲 JSX 編譯成React.createElement方法的調用,因此在你的 JSX 代碼中,React庫必須也始終在做用域中。

好比,下面兩個導入都是必須的,儘管 React 和 CustomButton 都沒有在代碼中被直接調用。

import React from 'react';
import CustomButton from './CustomButton';

function WarningButton() {
  // return React.createElement(CustomButton, {color: 'red'}, null);
  return <CustomButton color="red" />;
}

 

若是你沒有使用JavaScript 打捆機,而是從<script>標籤加載React,它已經在做用域中,以React全局變量的形式。

點表示法用於JSX類型

你還可使用 JSX 中的點表示法來引用 React 組件。你能夠方便地從一個模塊中導出許多 React 組件。例如,有一個名爲 MyComponents.DatePicker 的組件,你能夠直接在 JSX 中使用它:

import React from 'react';

const MyComponents = {
  DatePicker: function DatePicker(props) {
    return <div>Imagine a {props.color} datepicker here.</div>;
  }
}

function BlueDatePicker() {
  return <MyComponents.DatePicker color="blue" />;
}

 

用戶定義組件必須首字母大寫

當元素類型以小寫字母開頭時,它表示一個內置的組件,如 <div> 或 <span>,將致使字符串 'div' 或 'span' 傳遞給 React.createElement。 以大寫字母開頭的類型,如 <Foo /> 編譯爲 React.createElement(Foo),而且它正對應於你在 JavaScript 文件中定義或導入的組件。

咱們建議用大寫開頭命名組件。若是你的組件以小寫字母開頭,請在 JSX 中使用以前其賦值給大寫開頭的變量。

例如,下面的代碼將沒法按預期運行:

import React from 'react';

// 錯誤!組件名應該首字母大寫:
function hello(props) {
  // 正確!div 是有效的 HTML 標籤:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 錯誤!React 會將小寫開頭的標籤名認爲是 HTML 原生標籤:
  return <hello toWhat="World" />;
}

 

爲了解決這個問題,咱們將 hello 重命名爲 Hello,而後使用 <Hello /> 引用:

import React from 'react';

// 正確!組件名應該首字母大寫:
function Hello(props) {
  // 正確!div 是有效的 HTML 標籤:
  return <div>Hello {props.toWhat}</div>;
}

function HelloWorld() {
  // 正確!React 可以將大寫開頭的標籤名認爲是 React 組件。
  return <Hello toWhat="World" />;
}

 

在運行時選擇類型

你不能使用一個通用的表達式來做爲 React 元素的標籤。若是你的確想使用一個通用的表達式來肯定 React 元素的類型,請先將其賦值給大寫開頭的變量。這種狀況通常發生於當你想基於屬性值渲染不一樣的組件時:

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 錯誤!JSX 標籤名不能爲一個表達式。
  return <components[props.storyType] story={props.story} />;
}

 

要解決這個問題,咱們須要先將類型賦值給大寫開頭的變量。

import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
  // 正確!JSX 標籤名能夠爲大寫開頭的變量。
  const SpecificStory = components[props.storyType];
  return <SpecificStory story={props.story} />;
}

 

JSX的屬性(Props)

在 JSX 中有幾種不一樣的方式來指定屬性。

使用 JavaScript 表達式做爲屬性

你能夠傳遞JavaScript 表達式做爲一個屬性,再用大括號{}括起來。例如,在這個 JSX 中:

<MyComponent foo={1 + 2 + 3 + 4} />

對於 MyComponent來講, props.foo 的值爲 10,這是 1 + 2 + 3 + 4 表達式計算得出的。

if 語句和 for 循環在 JavaScript 中不是表達式,所以它們不能直接在 JSX 中使用,可是你能夠將它們放在周圍的代碼中。例如:

function NumberDescriber(props) {
  let description;
  if (props.number % 2 == 0) {
    description = <strong>even</strong>;
  } else {
    description = <i>odd</i>;
  }
  return <div>{props.number} is an {description} number</div>;
}

 

你能夠在相關部分中瞭解有關 條件渲染 和 循環 的更多信息。

字符串常量

你能夠將字符串常量做爲屬性值傳遞。下面這兩個 JSX 表達式是等價的:

<MyComponent message="hello world" />

<MyComponent message={'hello world'} />

 

當傳遞一個字符串常量時,該值爲HTML非轉義的,因此下面兩個 JSX 表達式是相同的:

<MyComponent message="&lt;3" /> <MyComponent message={'<3'} />

這種行爲一般是無心義的,提到它只是爲了完整性。

屬性默認爲「True」

若是你沒有給屬性傳值,它默認爲 true。所以下面兩個 JSX 是等價的:

<MyTextBox autocomplete /> <MyTextBox autocomplete={true} />

通常狀況下,咱們不建議這樣使用,由於它會與 ES6 對象簡潔表示法 混淆。好比 {foo} 是 {foo: foo} 的簡寫,而不是 {foo: true}。這裏能這樣用,是由於它符合 HTML 的作法。

展開屬性

若是你已經有了個 props 對象,而且想在 JSX 中傳遞它,你可使用 ... 做爲「展開(spread)」操做符來傳遞整個屬性對象。下面兩個組件是等效的:

function App1() {
  return <Greeting firstName="Ben" lastName="Hector" />;
}

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

 

You can also pick specific props that your component will consume while passing all other props using the spread operator.

const Button = props => {
  const { kind, ...other } = props;
  const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
  return <button className={className} {...other} />;
};

const App = () => {
  return (
    <div>
      <Button kind="primary" onClick={() => console.log("clicked!")}>
        Hello World!
      </Button>
    </div>
  );
};

 

In the example above, the kind prop is safely consumed and is not passed on to the <button> element in the DOM. All other props are passed via the ...otherobject making this component really flexible. You can see that it passes an onClick and children props.

展開屬性很是有用。可是他們也容易傳遞沒必要要的屬性給組件,而組件並不須要這些多餘屬性。或者傳遞無效的HTML熟悉給DOM。咱們建議你謹慎使用此語法。

JSX中的子代

在既包含開始標籤又包含結束標籤的 JSX 表達式中,這兩個標籤之間的內容被傳遞爲專門的屬性:props.children。有幾種不一樣的方法來傳遞子代:

字符串字面量

你能夠在開始和結束標籤之間放入一個字符串,則 props.children 就是那個字符串。這對於許多內置 HTML 元素頗有用。例如:

<MyComponent>Hello world!</MyComponent>

 

這是有效的 JSX,而且 MyComponent 的 props.children 值將會直接是 "hello world!"。由於 HTML 未轉義,因此你能夠像寫 HTML 同樣寫 JSX:

<div>This is valid HTML &amp; JSX at the same time.</div>

 

JSX 會移除空行和開始與結尾處的空格。標籤鄰近的新行也會被移除,字符串常量內部的換行會被壓縮成一個空格,因此下面這些都等價:

<div>Hello World</div>

<div>
  Hello World
</div>

<div>
  Hello
  World
</div>

<div>

  Hello World
</div>

 

JSX子代

你能夠提供更多個 JSX 元素做爲子代,這對於嵌套顯示組件很是有用:

<MyContainer>
  <MyFirstComponent />
  <MySecondComponent />
</MyContainer>

 

你能夠混合不一樣類型的子代,同時使用字符串字面量和 JSX子代,這是 JSX 相似 HTML 的另外一種形式,這在 JSX 和 HTML 中都是有效的:

<div>
  Here is a list:
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>

 

React 組件也能夠返回包含多個元素的一個數組:

render() {
  // 不須要使用額外的元素包裹數組中的元素!
  return [
    // 不要忘記 key :)
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

 

JavaScript 表達式做爲子代

你能夠將任何 {} 包裹的 JavaScript 表達式做爲子代傳遞。例如,下面這些表達式是等價的:

<MyComponent>foo</MyComponent>

<MyComponent>{'foo'}</MyComponent>

 

這對於渲染任意長度的 JSX 表達式的列表頗有用。例如,下面將會渲染一個 HTML 列表:

function Item(props) {
  return <li>{props.message}</li>;
}

function TodoList() {
  const todos = ['finish doc', 'submit pr', 'nag dan to review'];
  return (
    <ul>
      {todos.map((message) => <Item key={message} message={message} />)}
    </ul>
  );
}

 

JavaScript 表達式能夠與其餘類型的子代混合使用。這一般對於字符串模板很是有用:

function Hello(props) {
  return <div>Hello {props.addressee}!</div>;
}

 

布爾值、Null 和 Undefined 被忽略

falsenullundefined 和 true 都是有效的子代,只是它們不會被渲染。下面的JSX表達式將渲染爲相同的東西:

<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

 

這在根據條件來肯定是否渲染React元素時很是有用。如下的JSX只會在showHeadertrue時渲染<Header />組件。

<div>
  {showHeader && <Header />}
  <Content />
</div>

 

一個告誡是JavaScript中的一些 "falsy" 值(好比數字0),它們依然會被React渲染。例如,下面的代碼不會像你預期的那樣運行,由於當 props.message 爲空數組時,它會打印0:

<div>
  {props.messages.length &&
    <MessageList messages={props.messages} />
  }
</div>

 

要解決這個問題,請確保 && 前面的表達式始終爲布爾值:

<div>
  {props.messages.length > 0 &&
    <MessageList messages={props.messages} />
  }
</div>

 

相反,若是你想讓相似 falsetruenull 或 undefined 出如今輸出中,你必須先把它轉換成字符串 :

<div>
  My JavaScript variable is {String(myVariable)}.
</div>

 

使用 PropTypes 進行類型檢查

使用 prop-types 庫幫助咱們對組件的屬性進行類型的控制。

隨着應用日漸龐大,你能夠經過類型檢查捕獲大量錯誤。 對於某些應用來講,你還可使用 Flow 或 TypeScript 這樣的 JavsScript 擴展來對整個應用程序進行類型檢查。然而即便你不用它們,React 也有一些內置的類型檢查功能。要檢查組件的屬性,你須要配置特殊的 propTypes 屬性:

import PropTypes from 'prop-types';

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

Greeting.propTypes = {
  name: PropTypes.string
};

 

PropTypes 包含一整套驗證器,可用於確保你接收的數據是有效的。在這個示例中,咱們使用了 PropTypes.string。當你給屬性傳遞了無效值時,JavsScript 控制檯將會打印警告。出於性能緣由,propTypes只在開發模式下進行檢查。

PropTypes

下面是使用不一樣驗證器的例子:

import PropTypes from 'prop-types';

MyComponent.propTypes = {
  // 你能夠將屬性聲明爲如下 JS 原生類型
  optionalArray: PropTypes.array,
  optionalBool: PropTypes.bool,
  optionalFunc: PropTypes.func,
  optionalNumber: PropTypes.number,
  optionalObject: PropTypes.object,
  optionalString: PropTypes.string,
  optionalSymbol: PropTypes.symbol,

  // 任何可被渲染的元素(包括數字、字符串、子元素或數組)。
  optionalNode: PropTypes.node,

  // 一個 React 元素
  optionalElement: PropTypes.element,

  // 你也能夠聲明屬性爲某個類的實例,這裏使用 JS 的
  // instanceof 操做符實現。
  optionalMessage: PropTypes.instanceOf(Message),

  // 你也能夠限制你的屬性值是某個特定值之一
  optionalEnum: PropTypes.oneOf(['News', 'Photos']),

  // 限制它爲列舉類型之一的對象
  optionalUnion: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
    PropTypes.instanceOf(Message)
  ]),

  // 一個指定元素類型的數組
  optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

  // 一個指定類型的對象
  optionalObjectOf: PropTypes.objectOf(PropTypes.number),

  // 一個指定屬性及其類型的對象
  optionalObjectWithShape: PropTypes.shape({
    color: PropTypes.string,
    fontSize: PropTypes.number
  }),

  // 你也能夠在任何 PropTypes 屬性後面加上 `isRequired` 
  // 後綴,這樣若是這個屬性父組件沒有提供時,會打印警告信息
  requiredFunc: PropTypes.func.isRequired,

  // 任意類型的數據
  requiredAny: PropTypes.any.isRequired,

  // 你也能夠指定一個自定義驗證器。它應該在驗證失敗時返回
  // 一個 Error 對象而不是 `console.warn` 或拋出異常。
  // 不過在 `oneOfType` 中它不起做用。
  customProp: function(props, propName, componentName) {
    if (!/matchme/.test(props[propName])) {
      return new Error(
        'Invalid prop `' + propName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  },

  // 不過你能夠提供一個自定義的 `arrayOf` 或 `objectOf` 
  // 驗證器,它應該在驗證失敗時返回一個 Error 對象。 它被用
  // 於驗證數組或對象的每一個值。驗證器前兩個參數的第一個是數組
  // 或對象自己,第二個是它們對應的鍵。
  customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
    if (!/matchme/.test(propValue[key])) {
      return new Error(
        'Invalid prop `' + propFullName + '` supplied to' +
        ' `' + componentName + '`. Validation failed.'
      );
    }
  })
};

 

限制單個子代

使用 PropTypes.element 你能夠指定只傳遞一個子代

import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    // This must be exactly one element or it will warn.
    const children = this.props.children;
    return (
      <div>
        {children}
      </div>
    );
  }
}

MyComponent.propTypes = {
  children: PropTypes.element.isRequired
};

 

屬性默認值

你能夠經過配置 defaultProps 爲 props定義默認值:

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

// 爲屬性指定默認值:
Greeting.defaultProps = {
  name: 'Stranger'
};

// 渲染 "Hello, Stranger":
ReactDOM.render(
  <Greeting />,
  document.getElementById('example')
);

 

若是你在使用像 transform-class-properties 的 Babel 轉換器,你也能夠在React 組件類中聲明 defaultProps 做爲靜態屬性。這個語法尚未最終經過,在瀏覽器中須要一步編譯工做。更多信息,查看類字段提議

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }

  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

 

defaultProps 用來確保 this.props.name 在父組件沒有特別指定的狀況下,有一個初始值。類型檢查發生在 defaultProps 賦值以後,因此類型檢查也會應用在 defaultProps 上面。

Refs & DOM

Refs 提供了一種方式,用於訪問在 render 方法中建立的 DOM 節點或 React 元素。

在典型的 React 數據流中, 屬性(props)是父組件與子組件交互的惟一方式。要修改子組件,你須要使用新的 props 從新渲染它。可是,某些狀況下你須要在典型數據流外強制修改子組件。要修改的子組件能夠是 React 組件的實例,也能夠是 DOM 元素。對於這兩種狀況,React 提供瞭解決辦法。

什麼時候使用 Refs

下面是幾個適合使用 refs 的狀況:

  • 處理焦點、文本選擇或媒體控制。
  • 觸發強制動畫。
  • 集成第三方 DOM 庫

若是能夠經過聲明式實現,則儘可能避免使用 refs。

例如,不要在 Dialog 組件上直接暴露 open() 和 close() 方法,最好傳遞 isOpen 屬性。

不要過分使用 Refs

你可能首先會想到在你的應用程序中使用 refs 來更新組件。若是是這種狀況,請花一點時間,從新思考一下 state 屬性在組件層中位置。一般你會想明白,提高 state 所在的組件層級會是更合適的作法。有關示例,請參考狀態提高.

Note

下面的例子已經用 React v16.3 引入的 React.createRef() API 更新。若是你正在使用 React 更早的發佈版,咱們推薦使用回調形式的 refs

建立 Refs

使用 React.createRef() 建立 refs,經過 ref 屬性來得到 React 元素。當構造組件時,refs 一般被賦值給實例的一個屬性,這樣你能夠在組件中任意一處使用它們.

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return <div ref={this.myRef} />;
  }
}

 

訪問 Refs

當一個 ref 屬性被傳遞給一個 render 函數中的元素時,可使用 ref 中的 current 屬性對節點的引用進行訪問。

const node = this.myRef.current;

 

ref的值取決於節點的類型:
  • 當 ref 屬性被用於一個普通的 HTML 元素時,React.createRef() 將接收底層 DOM 元素做爲它的 current屬性以建立 ref 。
  • 當 ref 屬性被用於一個自定義類組件時,ref 對象將接收該組件已掛載的實例做爲它的 current 。
  • 你不能在函數式組件上使用 ref 屬性,由於它們沒有實例。

下面的例子說明了這些差別。

爲 DOM 元素添加 Ref

如下代碼使用 ref 存儲對 DOM 節點的引用:

class CustomTextInput extends React.Component {
  constructor(props) {
    super(props);
    // 建立 ref 存儲 textInput DOM 元素
    this.textInput = React.createRef();
    this.focusTextInput = this.focusTextInput.bind(this);
  }

  focusTextInput() {
    // 直接使用原生 API 使 text 輸入框得到焦點
    // 注意:經過 "current" 取得 DOM 節點
    this.textInput.current.focus();
  }

  render() {
    // 告訴 React 咱們想把 <input> ref 關聯到構造器裏建立的 `textInput` 上
    return (
      <div>
        <input
          type="text"
          ref={this.textInput} />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

 

React 會在組件加載時將 DOM 元素傳入 current 屬性,在卸載時則會改回 nullref 的更新會發生在componentDidMount 或 componentDidUpdate 生命週期鉤子以前。

爲類組件添加 Ref

若是咱們想要包裝上面的 CustomTextInput ,來模擬掛載以後當即被點擊的話,咱們可使用 ref 來訪問自定義輸入,並手動調用它的 focusTextInput 方法:

class AutoFocusTextInput extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }

  componentDidMount() {
    this.textInput.current.focusTextInput();
  }

  render() {
    return (
      <CustomTextInput ref={this.textInput} />
    );
  }
}

 

須要注意的是,這種方法僅對 class 聲明的 CustomTextInput 有效:

class CustomTextInput extends React.Component {
  // ...
}

 

Refs 與函數式組件

你不能在函數式組件上使用 ref 屬性,由於它們沒有實例:

function MyFunctionalComponent() {
  return <input />;
}

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.textInput = React.createRef();
  }
  render() {
    // 這將 *不會* 工做!
    return (
      <MyFunctionalComponent ref={this.textInput} />
    );
  }
}

 

若是你想使用 ref,就像你想使用生命週期方法或者 state 同樣,應該將其轉換爲 class 組件。

可是,你能夠在函數式組件內部使用 ref,只要它指向一個 DOM 元素或者 class 組件:

function CustomTextInput(props) {
  // 這裏必須聲明 textInput,這樣 ref 回調才能夠引用它
  let textInput = null;

  function handleClick() {
    textInput.focus();
  }

  return (
    <div>
      <input
        type="text"
        ref={(input) => { textInput = input; }} />
      <input
        type="button"
        value="Focus the text input"
        onClick={handleClick}
      />
    </div>
  );  
}

 

對父組件暴露 DOM 節點

在極少數狀況下,你可能但願從父組件訪問子節點的 DOM 節點。一般不建議這樣作,由於它會破壞組件的封裝,但偶爾也可用於觸發焦點或測量子 DOM 節點的大小或位置。

雖然你能夠向子組件添加 ref,但這不是一個理想的解決方案,由於你只能獲取組件實例而不是 DOM 節點。而且,它還在函數式組件上無效。

若是你使用 React 16.3 或更高, 這種狀況下咱們推薦使用 ref 轉發。 Ref 轉發使組件能夠像暴露本身的 ref 同樣暴露子組件的 ref。關於怎樣對父組件暴露子組件的 DOM 節點,在 ref 轉發文檔 中有一個詳細的例子。

若是你使用 React 16.2 或更低,或者你須要比 ref 轉發更高的靈活性,你可使用 這個替代方案 將 ref 做爲特殊名字的 prop 直接傳遞。

可能的話,咱們不建議暴露 DOM 節點,但有時候它會成爲救命稻草。注意這些方案須要你在子組件中增長一些代碼。若是你對子組件的實現沒有控制權的話,你剩下的選擇是使用 findDOMNode(),可是不推薦。

回調 Refs

React 也支持另外一種設置 ref 的方式,稱爲「回調 ref」,更加細緻地控制什麼時候 ref 被設置和解除。

不一樣於傳遞 createRef() 建立的 ref 屬性,你會傳遞一個函數。這個函數接受 React 組件的實例或 HTML DOM 元素做爲參數,以存儲它們並使它們能被其餘地方訪問。

下面的例子描述了一種通用的範例:使用 ref 回調函數,在實例的屬性中存儲對 DOM 節點的引用。

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

    this.textInput = null;

    this.setTextInputRef = element => {
      this.textInput = element;
    };

    this.focusTextInput = () => {
      // 直接使用原生 API 使 text 輸入框得到焦點
      if (this.textInput) this.textInput.focus();
    };
  }

  componentDidMount() {
    // 渲染後文本框自動得到焦點
    this.focusTextInput();
  }

  render() {
    // 使用 `ref` 的回調將 text 輸入框的 DOM 節點存儲到 React
    // 實例上(好比 this.textInput)
    return (
      <div>
        <input
          type="text"
          ref={this.setTextInputRef}
        />
        <input
          type="button"
          value="Focus the text input"
          onClick={this.focusTextInput}
        />
      </div>
    );
  }
}

 

React 將在組件掛載時將 DOM 元素傳入ref 回調函數並調用,當卸載時傳入 null 並調用它。ref 回調函數會在 componentDidMount 和 componentDidUpdate 生命週期函數前被調用

你能夠在組件間傳遞迴調形式的 refs,就像你能夠傳遞經過 React.createRef() 建立的對象 refs 同樣。

function CustomTextInput(props) {
  return (
    <div>
      <input ref={props.inputRef} />
    </div>
  );
}

class Parent extends React.Component {
  render() {
    return (
      <CustomTextInput
        inputRef={el => this.inputElement = el}
      />
    );
  }
}

 

在上面的例子中,Parent 傳遞給它的 ref 回調函數做爲 inputRef 傳遞給 CustomTextInput,而後 CustomTextInput 經過 ref屬性將其傳遞給 <input>。最終,Parent 中的 this.inputElement 將被設置爲與 CustomTextIput 中的 <input> 元素相對應的 DOM 節點

舊版 API:String 類型的 Refs

若是你以前使用過 React ,你可能瞭解過以前的API中的 string 類型的 ref 屬性,好比 "textInput" ,你能夠經過 this.refs.textInput 訪問DOM節點。咱們不建議使用它,由於 String 類型的 refs 存在問題。它已過期並可能會在將來的版本被移除。若是你目前還在使用 this.refs.textInput 這種方式訪問 refs ,咱們建議用回調函數的方式代替。

注意

若是 ref 回調之內聯函數的方式定義,在更新期間它會被調用兩次,第一次參數是 null ,以後參數是 DOM 元素。這是由於在每次渲染中都會建立一個新的函數實例。所以,React 須要清理舊的 ref 而且設置新的。經過將 ref 的回調函數定義成類的綁定函數的方式能夠避免上述問題,可是大多數狀況下可有可無。

ajax請求

react的組件中,通常咱們在 componentDidMount事件中作ajax請求,並得到數據後修改state。

請求後臺獲取數據能夠用任何的ajax庫,建議使用: 原生的fetch或者axios庫。

例如新聞案例:

import React, { Component } from 'react'
import axios from 'axios';

class NewsList extends Component {
  constructor(opt) {
    super(opt);
    this.state = {
      newsList: []
    };
  }

  componentDidMount() {
    // 發送ajax請求到後臺並獲取數據
    axios
      .get('/db.json')
      .then(res => {
        // console.log(res.data.news);
        this.setState({newsList: res.data.news});
      });
  }

  delNews(id) {
    // 不模擬從後臺ajax請求刪除數據
    // 直接在當前數組中移除數據。
    if(window.confirm('您是否要真的刪除嗎?')) {
      this.setState(preState => {
        return {
          newsList: preState.newsList.filter( item => item.id !== id)
        }
      });
    }
  }

  render () {
    return (
      <div>
        <table className="table is-striped is-hoverable is-bordered is-fullwidth">
          <thead>
            <tr>
              <th>編號</th>
              <th>新聞標題</th>
              <th>編輯</th>
            </tr>
          </thead>
          <tbody>
          {
            this.state.newsList.map((item, index) => {
              return (
                <tr key={index}>
                  <td>{item.id}</td>
                  <td>{item.title}</td>
                  <td>
                    <button
                      className="button is-primary" 
                      onClick={ this.delNews.bind(this, item.id) }
                    >
                      刪除
                    </button>
                  </td>
                </tr>
              )
            })
          }
          </tbody>
        </table>
      </div>
    )
  }
}

export default NewsList;

 

React和DOM之間的屬性區別

React實現了一套與瀏覽器無關的DOM系統,兼顧了性能和跨瀏覽器的兼容性。在React和Html之間有許多屬性的行爲是不一樣的。

checked

checked屬性受類型爲checkboxradio<input>組件的支持。你能夠用它來設定是否組件是被選中的。這對於構建受控組件頗有用。與之相對defaultChecked這是非受控組件的屬性,用來設定對應組件首次裝載時是否選中狀態。

className

使用className屬性指定一個CSS類。這個特性適用於全部的常規DOM節點和SVG元素,好比<div><a>和其它的元素。

若是你在React中使用Web組件(這是一種不常見的使用方式),請使用class屬性來代替。

dangerouslySetInnerHTML

dangerouslySetInnerHTML是React提供的替換瀏覽器DOM中的innerHTML接口的一個函數。通常而言,使用JS代碼設置HTML文檔的內容是危險的,由於這樣很容易把你的用戶信息暴露給跨站腳本攻擊.因此,你雖然能夠直接在React中設置html的內容,但你要使用 dangerouslySetInnerHTML 並向該函數傳遞一個含有__html鍵的對象,用來提醒你本身這樣作很危險。例如:

function createMarkup() {
  return {__html: 'First &middot; Second'};
}

function MyComponent() {
  return <div dangerouslySetInnerHTML={createMarkup()} />;
}

 

htmlFor

由於for是在javascript中的一個保留字,React元素使用 htmlFor代替。

onChange

onChange事件的行爲正如你所指望的:不管一個表單字段什麼時候發生變化,這個事件都會被觸發。咱們故意不使用瀏覽器已有的默認行爲,由於onChange在瀏覽器中的行爲和名字不相稱,React依靠這個事件實時處理用戶輸入。

selected

selected屬性被<option>組件支持。你可使用該屬性設定組件是否被選擇。這對構建受控組件頗有用。

style

style屬性接受一個JavaScript對象,其屬性用小駝峯命名法命名,而不是接受CSS字符串。這和DOM中styleJavaScript 屬性是一致性的,是更高效的,並且可以防止XSS的安全漏洞。例如:

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

 

注意樣式不會自動補齊前綴。爲了支持舊的瀏覽器,你須要手動提供相關的樣式屬性:

const divStyle = {
  WebkitTransition: 'all', // note the capital 'W' here
  msTransition: 'all' // 'ms' is the only lowercase vendor prefix
};

function ComponentWithTransition() {
  return <div style={divStyle}>This should work cross-browser</div>;
}

 

樣式key使用小駝峯命名法是爲了從JS中訪問DOM節點的屬性保持一致性(例如 node.style.backgroundImage)。供應商前綴除了ms,都應該以大寫字母開頭。這就是爲何WebkitTransition有一個大寫字母W

React將自動添加一個"px"後綴到某些數字內聯樣式屬性。若是你但願使用不一樣於"px"的其餘單位,指定值爲帶渴望單位的字符串。例如:

// Result style: '10px'
<div style={{ height: 10 }}>
  Hello World!
</div>

// Result style: '10%'
<div style={{ height: '10%' }}>
  Hello World!
</div>

 

不是全部樣式屬性被轉化爲像素字符串,儘管如此。某些個保持無單位(例如 zoomorderflex)。A complete list of 無單位屬性 can be seen here.

value

value屬性受到<input> 和 <textarea> 組件的支持。你可使用它設置組件的值。這對構建受控組件很是有用。defaultValue屬性對應的是非受控組件的屬性,用來設置組件第一次裝載時的值。

其餘受支持的HTML屬性

As of React 16, 任何標準的或自定義的 DOM屬性都被充分支持。

React 老是提供一個以 JavaScript爲中心的API給DOM。由於React組件對於自定義和DOM相關的屬性都常常採用。React使用小駝峯約定,正如DOM API:

<div tabIndex="-1" />      // Just like node.tabIndex DOM API
<div className="Button" /> // Just like node.className DOM API
<input readOnly={true} />  // Just like node.readOnly DOM API

 

這些屬性的工做相似於對應的HTML屬性,除了上述文檔的特例。

(Context)上下文

Context 經過組件樹提供了一個傳遞數據的方法,從而避免了在每個層級手動的傳遞 props 屬性。

在一個典型的 React 應用中,數據是經過 props 屬性由上向下(由父及子)的進行傳遞的,但這對於某些類型的屬性而言是極其繁瑣的(例如:地區偏好,UI主題),這是應用程序中許多組件都所須要的。 Context 提供了一種在組件之間共享此類值的方式,而沒必要經過組件樹的每一個層級顯式地傳遞 props

簡單說就是,當你不想在組件樹中經過逐層傳遞props或者state的方式來傳遞數據時,可使用Context來實現跨層級的組件數據傳遞。

  • 使用props或者state傳遞數據,數據自頂下流。

數據自頂下流

  • 使用Context,能夠跨越組件進行數據傳遞

跨越組件進行數據傳遞

什麼時候使用 Context

Context 設計目的是爲共享那些被認爲對於一個組件樹而言是「全局」的數據,例如當前認證的用戶、主題或首選語言。例如,在下面的代碼中,咱們經過一個「theme」屬性手動調整一個按鈕組件的樣式:

function ThemedButton(props) {
  return <Button theme={props.theme} />;
}

// 中間組件
function Toolbar(props) {
  // Toolbar 組件必須添加一個額外的 theme 屬性
  // 而後傳遞它給 ThemedButton 組件
  return (
    <div>
      <ThemedButton theme={props.theme} />
    </div>
  );
}

class App extends React.Component {
  render() {
    return <Toolbar theme="dark" />;
  }
}

 

使用 context, 我能夠避免經過中間元素傳遞 props:

// 建立一個 theme Context,  默認 theme 的值爲 light
const ThemeContext = React.createContext('light');

function ThemedButton(props) {
  // ThemedButton 組件從 context 接收 theme
  return (
    <ThemeContext.Consumer>
      {theme => <Button {...props} theme={theme} />}
    </ThemeContext.Consumer>
  );
}

// 中間組件
function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

 

注意

不要僅僅爲了不在幾個層級下的組件傳遞 props 而使用 context,它是被用於在多個層級的多個組件須要訪問相同數據的情景。

React.createContext

const {Provider, Consumer} = React.createContext(defaultValue);

 

建立一對 { Provider, Consumer }。當 React 渲染 context 組件 Consumer 時,它將從組件樹的上層中最接近的匹配的 Provider 讀取當前的 context 值。

若是上層的組件樹沒有一個匹配的 Provider,而此時你須要渲染一個 Consumer 組件,那麼你能夠用到 defaultValue 。這有助於在不封裝它們的狀況下對組件進行測試。

Provider

<Provider value={/* some value */}>

 

React 組件容許 Consumers 訂閱 context 的改變。

接收一個 value 屬性傳遞給 Provider 的後代 Consumers。一個 Provider 能夠聯繫到多個 Consumers。Providers 能夠被嵌套以覆蓋組件樹內更深層次的值。

Consumer

<Consumer>
  {value => /* render something based on the context value */}
</Consumer>

 

一個能夠訂閱 context 變化的 React 組件。

接收一個 函數做爲子節點. 函數接收當前 context 的值並返回一個 React 節點。傳遞給函數的 value 將等於組件樹中上層 context 的最近的 Provider 的 value 屬性。若是 context 沒有 Provider ,那麼 value 參數將等於被傳遞給 createContext() 的 defaultValue 。

注意

關於此案例的更多信息, 請看 render props.

每當Provider的值發生改變時, 做爲Provider後代的全部Consumers都會從新渲染。 從Provider到其後代的Consumers傳播不受shouldComponentUpdate方法的約束,所以即便祖先組件退出更新時,後代Consumer也會被更新。

經過使用與Object.is相同的算法比較新值和舊值來肯定變化。

注意

(這在傳遞對象做爲 value 時會引起一些問題Caveats.)

動態 Context

一個更加複雜的例子:

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

const ThemeContext = React.createContext({
  background: 'red',
  color: 'white'
});

 

經過靜態方法React.createContext()建立一個Context對象,這個Context對象包含兩個組件,<Provider /><Consumer />

class App extends React.Component {
  render () {
    return (
      <ThemeContext.Provider value={{background: 'green', color: 'white'}}>
        <Header />
      </ThemeContext.Provider>
    );
  }
}

 

複製代碼<Provider />的value至關於如今的getChildContext()

class Header extends React.Component {
  render () {
    return (
      <Title>Hello React Context API</Title>
    );
  }
}
class Title extends React.Component {
  render () {
    return (
      <ThemeContext.Consumer>
        {context => (
          <h1 style={{background: context.background, color: context.color}}>
            {this.props.children}
          </h1>
        )}
      </ThemeContext.Consumer>
    );
  }
}

 

複製代碼<Consumer />children必須是一個函數,經過函數的參數獲取<Provider />提供的Context

幾個能夠直接獲取Context的地方

實際上,除了實例的context屬性(this.context),React組件還有不少個地方能夠直接訪問父組件提供的Context。好比構造方法:

constructor(props, context)

好比生命週期:

componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)

 

對於面向函數的無狀態組件,能夠經過函數的參數直接訪問組件的Context。

const StatelessComponent = (props, context) => ( ...... )

做用於多個上下文

爲了保持 context 快速進行二次渲染, React 須要使每個 Consumer 在組件樹中成爲一個單獨的節點。

// 主題上下文, 默認light
const ThemeContext = React.createContext('light');

// 登錄用戶上下文
const UserContext = React.createContext();

// 一個依賴於兩個上下文的中間組件
function Toolbar(props) {
  return (
    <ThemeContext.Consumer>
      {theme => (
        <UserContext.Consumer>
          {user => (
            <ProfilePage user={user} theme={theme} />
          )}
        </UserContext.Consumer>
      )}
    </ThemeContext.Consumer>
  );
}

class App extends React.Component {
  render() {
    const {signedInUser, theme} = this.props;

    // App組件提供上下文的初始值
    return (
      <ThemeContext.Provider value={theme}>
        <UserContext.Provider value={signedInUser}>
          <Toolbar />
        </UserContext.Provider>
      </ThemeContext.Provider>
    );
  }
}

 

若是兩個或者多個上下文的值常常被一塊兒使用,也許你須要考慮你本身渲染屬性的組件提供給它們。

實例

import React, { Component } from 'react'

const LocalContext = React.createContext();

const { Provider, Consumer } = LocalContext; 

function Container(props) {
  return <Title />
}

function Title(props) {
  return (
    <div>
      <Consumer>
        { context => {
          return (
            <div>
              {context.name} - { context.age}
            </div>
          )
        }}
      </Consumer>
    </div>
  )
}

class DM extends Component {
  componentDidMount() {
    console.log(this.props);
  }

  render() {
    return (
      <div>
        <Consumer>
          {
            user => (<div>
              <p>{ user.name }</p>
              <p>{ user.age }</p>
            </div>
            )
          }
        </Consumer>
        ---{ this.props.name }----
      </div>
    )
  }

}

class ContextDemo extends Component {
  constructor (props, context) {
    super(props, context)
    this.state = {
      User: {
        age: 19,
        name: 'aicoder'
      }
    }
  }


  render () {
    return (
      <div>
        <Provider value={ this.state.User }>
          <Container></Container>
          <DM></DM>
        </Provider>
        <hr/>
        <input
          onClick={
            () => this.setState(preState => {
              return {User: { ...preState.User, age: preState.User.age + 1 }}
            })
          }
          className="button is-primary"
          value={ this.state.User.name }
          type="button"
          />
      </div>
    )
  }
}

export default ContextDemo
相關文章
相關標籤/搜索