如果你曾經使用過類似 Angular
框架的經驗,你會發現這些框架整合了開發一個應用應該具備的所有功能,例如用於進行HTTP呼叫的服務(Angular
中的 $HTTP
)。
React
是一個檢視層框架,用於構建使用者介面。在 MVC
架構中,它僅僅負責檢視部分。在實際的開發過程中,往往需要我們引入一些第三方元件,比如今天要說的 AJAX API
。
本文通過一個簡單的示例,學習通過不同的方式(如 Axios
、 XMLHttpRequest
或 fetch API
)使用來進行 AJAX
請求(GET、POST、PUT和DELETE),以獲取、建立、更新和刪除資料。
AJAX API
有很多的方案, 你所做的就是選擇合適的解決方案:
1、對JavaScript
中 Promise
比較熟悉,可以使用 Axios。
2、喜歡使用前沿標準,可以使用 fetch API
。
3、也可以使用 jQuery
,但不建議僅僅為了進行API呼叫而引入整個庫。
可以直接使用 XMLHttpRequest 介面。
Fetch API的示例
我推薦使用 create-react-app
來建立應用,因為它省去了好多的配置。
npm install -g create-react-app
複製程式碼
通過執行以下命令建立並啟動我們的應用:react-ajax-demo
create-react-app react-ajax-demo
cd react-ajax-demo
npm start
複製程式碼
Fetch API
下面引自Mozilla MDN:
Fetch API 提供了一個獲取資源的介面(包括跨域)。任何使用過 XMLHttpRequest 的人都能輕鬆上手,但新的API提供了更強大和靈活的功能集。
讀取Reddit上釋出的一些帖子,src/App.js
:
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
fetchFirst(url) {
var that = this;
if (url) {
fetch('https://www.reddit.com/r/' + url + '.json').then(function (response) {
return response.json();
}).then(function (result) {
that.setState({ posts: result.data.children, lastPostName: result.data.children[result.data.children.length - 1].data.name });
console.log(that.state.posts);
});
}
}
componentWillMount() {
this.fetchFirst("reactjs");
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">React AJAX Example</h1>
</header>
<p className="App-intro">
<ul>
{this.state.posts.map(post =>
<li key={post.id}>{post.title}</li>
)}
</ul>
</p>
</div>
);
}
}
export default App;
複製程式碼
在 fetchFirst()
方法中,我們發起了一個GET請求:
fetch('https://www.reddit.com/r/' + url + '.json').then(function (response) {
return response.json();
}).then(function (result) {
console.log(result.data.children);
});
複製程式碼
API很簡單,需要的唯一引數就是資源的URI。這裡它是一個JSON檔案,也可以是任何型別的資源,如影像或其他型別。
**fetch(url)**在預設情況下傳送GET HTTP請求。
還可以通過第二個引數中指定方法名來呼叫其他HTTP方法,如POST、PUT或DELETE。
例如,使用 fetch()
傳送POST請求:
var form = new FormData(document.getElementById('login-form'));
fetch("/login", {
method: "POST",
body: form
});
複製程式碼
AJAX API呼叫時機
一個典型的 React
應用是一系列包含根元件、父元件以及子元件(以及子元件的子元件)的元件,這些元件可以可看作為一個樹狀結構。
這裡會產生許多問題: 1、如何處理資料? 2、資料如何從元件流到其他元件? 3、最重要的是,在哪裡放非同步請求的程式碼?
componentWillMount()
是React生命週期的一個重要事件,執行時機在元件第一次渲染之前,在元件即將掛載時呼叫該事件。
前面的例子中,我們就是在 componentWillMount()
事件內部發起 AJAX
請求。
其實還有幾個地方可以這麼做:
1、componentDidMount()
: 這是官方推薦的地方
2、componentWillMount()
: 官方不推薦
3、constructor()
:它被認為是反模式! 但是可以像 componentWillMount()
一樣使用。
componentDidMount() vs componentWillMount()
componentWillMount()
方法在元件的第一次渲染之前被呼叫。任何人誤以為這是獲取資料的最佳場所。但事實並非如此!因為 fetch
呼叫是非同步的,這意味著在元件渲染之前非同步請求有可能還沒返回。一般採取的做法是:在 constructor()
設定初始狀態,為了 React
能正確地渲染元件,在非同步請求返回後觸發元件的重新渲染。
componentDidMount()
方法是在元件第一次渲染之後呼叫的,因此可以放心在這裡執行任何非同步程式碼,這些操作可以是獲取伺服器資料,也可以是操作DOM。
在實現服務端渲染時,componentWillMount()會呼叫兩次,因此會有兩次請求傳送給伺服器,這是一種浪費。而componentDidMount()只會在客戶端呼叫一次。
我們對上面的示例做下改動:
componentDidMount() {
this.fetchFirst("reactjs");
}
複製程式碼
componentWillMount() vs constructor
從程式的角度分析,是否可以用 constructor
代替 componentWillMount()
?
表面上看確實是可以替代的,但這並不是一種正確的方式。
因為我們如果學過純函式的話,都知道其實非同步請求是一種典型的有副作用的非純函式。如果元件建構函式中存非純函式,React
會丟擲一個警告, 而 componentWillMount()
中是允許非純函式的。如果程式碼中需要更新其他元件中的狀態,最好不要使用建構函式。
最後一個結論是:對於任何不產生副作用(如狀態更新)的初始化程式碼使用建構函式,否則使用componentDidMount()。
元件之間通訊
另一種方法是從父元件向子元件傳遞資料。當然,父元件也應該避免之前獲取資料的方法,那麼在哪裡呢?父節點可以使用React路由器鉤子(如 onEnter()
)獲取資料,然後通過 props
將資料傳遞給子節點。
在本文示例中,由於只有一個元件,但這可以很容易地分解為以下內容:
Fetch.js
export default {
fetchFirst: function(url){
fetch('https://www.reddit.com/r/' + url + '.json').then(function (response) {
return response.json();
}).then(function (result) {
return result.data.children;
});
}
}
複製程式碼
RedditPost.js
import React from "react";
class RedditPost extends React.Component {
constructor(props) {
super(props);
this.state = {
title: props.title,
id: props.id
}
}
componentWillReceiveProps(nextProps) {
this.setState(nextProps);
}
render() {
<li key={this.state.id}>{this.state.title}</li>
);
}
}
}
RedditPost.propTypes = {
id: React.PropTypes.number,
title: React.PropTypes.string
};
RedditPost.defaultProps = {
id: null,
title: ""
};
export default RedditPost;
複製程式碼
App.js
import fetchFirst from "./Fetch";
import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
posts: []
};
}
componentDidMount() {
fetchFirst("reactjs").then((posts)=>{
this.state.posts = posts;
});
}
render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<h1 className="App-title">React AJAX Example</h1>
</header>
<p className="App-intro">
<ul>
{this.state.posts.map(post =>
<RedditPost {...post}/>
)}
</ul>
</p>
</div>
);
}
}
export default App;
複製程式碼
使用Redux State Manager
第三種方法是使用 Redux
這樣的狀態管理器。
從程式碼中的任何地方獲取資料,然後將資料儲存在全域性儲存中,所有其他元件都可以使用這些資料。
關於對 Redux
的學習,你可以自行了解,也可以讀一下我之前寫的幾篇文章:
1、Redux-如何更優雅的修改全域性狀態
2、重新思考Redux
結尾
越來越多的企業在構建自己的網站時開始注重體驗、效能,出現了大量的單頁面應用,將 AJAX
這種無刷技術發揮到了極致。而我們在使用 React
開發應用時可以有多種 AJAX
方案:
1、使用XMLHttpRequest
,如果覺得複雜,可直接使用 jQuery
。
2、Fetch API
, 現代瀏覽器都支援,老版瀏覽器需要使用 polyfills
。
3、Axios
:一個基於 promise
的 HTTP
庫, 可以用在瀏覽器和 node.js
中。