原文:An alternative to handle state in React: the URL !
作者:GaelS
譯者:博軒
React App 中的狀態
如何在 React App
中管理全局狀態,是所有類庫之間一直爭論不休的事情。然而,依我拙見,我們使用 URL
和 react-router
也可以做同樣的事情。
URL的勝利(FTW)
在單頁面應用中,URL
並不重要。大多數情況下,它只是一個請求所有資源的站點。
當你訪問 https://myApp.io ,還是訪問 https://myApp.io?user=gael&jo... 時,你第一次訪問頁面所看到的信息都是一樣的。
讓我們來解決這個問題。
譯註:由於國內被牆了,不能直接訪問 https://myApp.io 。我找了一個單頁面應用:https://www.souche.com。就是希望從首頁輸入的查詢條件,頁面跳轉之後,會出現在地址欄,並且頁面的狀態(查詢輸入框,分頁條件)會和地址欄中保持一致。
示例代碼
一開始,我在 first-contrib-app 項目中使用了這個想法。(代碼,以及演示)
但是,爲了這篇文章,我重新在 codesandbox 上面製作了一個示例,來專注於這個問題的解決方案。
首先,我們將如何使用 URL ?
我們將使用 URL 中,? 後面所包含的所有內容,就是所謂的搜索參數。
搜索參數的 MDN 鏈接
從 URL 中獲取信息
在本文的上下文中,我們將只使用一個查詢參數:query
。
爲了收集該參數(如果它確實存在於 URL,例如 https://myApp.io?query=javascript
),我們將會檢查搜索參數。幸運的是,他們可以在 window
對象中很容易找到。更準確的說,是 winndow.location.search
。
因此,當我們訪問 www.first-contrib?query=react
的使用,我們在控制檯打印會得到:
console.log(window.location.search); // "?query=react"
在理想情況下,格式化後的 JS
對象,會比字符串更加方便理解。爲了實現這一點,我們將使用瀏覽器的最新 API URLSearchParams
對象,而不是分割 URL 中的 =
和 ?
。除此之外,同樣可以使用 URLSearchParams
的 polyfill
版本。
代碼如下:
function getParams(location) {
const searchParams = new URLSearchParams(location.search);
return {
query: searchParams.get('query') || '',
};
}
因此,我們可以這樣使用:
const params = getParams('www.first-contrib.fr?query=react');
console.log(params) // { query: "react" }
現在,我們可以從 URL 中獲取一個參數對象,接下來將結合 react-router
,在我們的應用中使用。因此,我們將創建一個 router
來處理路由,並從 props
中獲取 route
屬性。
import React from "react";
import { render } from "react-dom";
import { BrowserRouter as Router, Route } from "react-router-dom";
// ...
// getParams code above
//a simple component to display
//the value of the query ...
// which is for now unknown
//so we'll instantiate it with an empty value
const MainPage = (props) => {
let query = '';
return (
<h2>{`Query : ${query}`}</h2>
);
}
const App = () => (
<React.Fragment>
<Router>
<React.Fragment>
<Route path="/" component={MainPage} />
</React.Fragment>
</Router>
</React.Fragment>
);
render(<App />, document.getElementById("root"));
爲了獲取查詢參數:query
的實際值,我們將使用 getParams
函數,在 MainPage
組件中,處理 從props
中獲取的 Route
對象:
<Route path="/" component={MainPage} />
如果我們打印 props
,我們將會得到:
{match: Object, location: Object, history: Object, /*other stuff */}
有趣的是,這裏的 location
對象,和之前的 window.location
結構很相似,這樣,我們操作會更簡單。因此,我們可以更新 MainPage
組件,讓他可以從 URL
中獲取值。
const MainPage = (props) => {
const { location } = props;
const { query } = getParams(location);
return (
<h2>{`My query: ${query}`}</h2>
);
}
現在,MainPage
可以使用 URL
了!
更新 URL (以及狀態)
現在,我們可以從 URL 中獲取信息,我們將實現一種方法,根據應用程序的狀態,來更新 URL。
爲此,我準備了一個簡單的輸入框示例:
class InputPage extends React.Component {
state = { inputValue: "" };
updateInputValue = e => this.setState({ inputValue: e.target.value });
render() {
return (
<React.Fragment>
<input
type="text"
placeholder="Change your URL !"
value={this.state.inputValue}
onChange={this.updateInputValue}
/>
<input type="button" value="Change the URL" onClick={null} />
</React.Fragment>
);
}
}
到目前爲止,我們的組件編輯內部狀態,來展示其當前的值。但是,我們仍然必須實現 onClick
函數來更新 URL,即使是相同的查詢參數。
我們可以看到從 Route
傳過來的 props
對象展示如下:
{match: Object, location:Object, history: Object, /*d'autres valeurs */}
這裏,我們關心的是 history
對象(有關 history
對象的其他信息在這裏...)
在 ReactRouter
文檔中,push
函數的示意如下:
將新的輸入,推送到歷史的堆棧當中
簡單來說,我們可以使用 push
方法來更新 URL !
因此,如果我們輸入的查詢條件是 javascript
,我們必須使用 www.myApp.io?query=javascript
來更新 URL
。因此,我們需要爲 URL 生成新的查詢參數。爲了實現這一目標, URLSearchParams
對象將再一次幫到我們。
function setParams({ query = ""}) {
const searchParams = new URLSearchParams();
searchParams.set("query", query);
return searchParams.toString();
}
請注意,當查詢參數:query
未定義,而且沒有默認值的時候,生成的 URL 將會是?query=undefined...
現在我們可以這樣寫:
const url = setParams({ query: "javascript" });
console.log(url); // "query=javascript"
我們可以在輸入組件中實現 onClick
。
class InputPage extends React.Component {
state = { inputValue: "" };
updateInputValue = e => this.setState({ inputValue: e.target.value });
updateURL = () => {
const url = setParams({ query: this.state.inputValue });
//do not forget the "?" !
this.props.history.push(`?${url}`);
};
render() {
return (
<React.Fragment>
<input
type="text"
className="input"
placeholder="What am I looking for ?"
value={this.state.inputValue}
onChange={this.updateInputValue}
/>
<input
type="button"
className="button"
value="Update the URL !"
onClick={this.updateURL}
/>
</React.Fragment>
);
}
}
現在,如果我們更改輸入的值,單擊按鈕我們將觸發 URL
的更新,MainPage
將相應地顯示新的值。
將應用程序的狀態保存在 URL 當中,最大的優勢在於當你複製,粘貼鏈接的時候。由於狀態包含在 URL 當中,我們的應用程序在首次加載的時候,將會保持這個狀態。
例如,當您在處理搜索引擎的時候,您可以在加載應用程序後立即觸發查詢。在這個first-contrib 應用中,我使用 react-apollo 很輕鬆的實現了。但是同樣,我們可以使用任何 HTTP 客戶端來實現相同的功能。
讓我們創建一個組件,使用 axios 處理請求,以及 Github REST API
(不需要任何登錄認證),使用一些生命週期方法來獲取 props
。
const httpClient = axios.create({
baseURL: "https://api.github.com"
});
class ResultsPage extends React.Component {
state = { results: [], loading: false, error: false };
//Search as soon as it is mounted !!
componentDidMount() {
return this.searchRepositories(this.props.query);
}
//Search as soon as query value is updated
componentWillReceiveProps(nextProps) {
if (nextProps.query !== this.props.query) {
this.setState({ query: nextProps.query });
return this.searchRepositories(nextProps.query);
}
}
searchRepositories = query => {
//handle if query is undefined
if (!query) {
return this.setState({
results: []
});
}
this.setState({ loading: true, error: false });
//the actual search on Github
return httpClient
.get(`/search/repositories?q=${query}`)
.then(({ data }) =>
this.setState({
results: data.items,
loading: false
})
)
.catch(e => this.setState({ loading: false, error: true }));
};
render() {
return (
<div>
{this.state.results.map(repo => (
<div key={repo.id}>
<a href={repo.html_url}>
{repo.name}
</a>
<div>{`by ${repo.owner.login}`}</div>
</div>
))}
</div>
);
}
}
就如同我們所看到的,我們現在有一個組件,只要更新 URL
中的查詢參數,就會觸發請求!
在我們的示例中,它只能處理一個名爲 query
的查詢參數,但是如果很多組件都可以來更新 URL 的狀態,這個用法將變得更加強大。例如,分頁,過濾,排序等也可以生成 URL 的參數。鏈接會是這個樣子:https://myApp.io?query=react&sort=ASC&filter=issues&page=2
。
代碼與我們之前的代碼類似。通過修改 URL ,可以更新 Route
組件所提供的 props
。然後,通過監聽 URL 中的特殊值,會觸發自身以及子組件的重新渲染。因此,它會使 UI 更新,以及觸發副作用,例如 HTTP 請求。
總結
就是這樣!這篇文章向您展示了在 React 應用中,一種處理全局狀態的備選方案。就包管理而言,它很輕(在現代瀏覽器中只有 0 KB ('▽')♪),使用,簡單,並可以爲應用帶來,直接可以訪問深層鏈接的效果,我覺得這很酷。 ( ̄y▽ ̄)~*捂嘴偷笑
希望對你有幫助!
譯註:我偷偷改了作者原來的顏文字...
本文已經聯繫原文作者,並授權翻譯,轉載請保留原文鏈接