原文: An alternative to handle state in React: the URL !
做者:GaelS
譯者:博軒
如何在 React App
中管理全局狀態,是全部類庫之間一直爭論不休的事情。然而,依我拙見,咱們使用 URL
和 react-router
也能夠作一樣的事情。javascript
在單頁面應用中,URL
並不重要。大多數狀況下,它只是一個請求全部資源的站點。html
當你訪問 https://myApp.io ,仍是訪問 https://myApp.io?user=gael&jo... 時,你第一次訪問頁面所看到的信息都是同樣的。java
讓咱們來解決這個問題。react
譯註:因爲國內被牆了,不能直接訪問 https://myApp.io 。我找了一個單頁面應用: https://www.souche.com。就是但願從首頁輸入的查詢條件,頁面跳轉以後,會出如今地址欄,而且頁面的狀態(查詢輸入框,分頁條件)會和地址欄中保持一致。
一開始,我在 first-contrib-app 項目中使用了這個想法。(代碼,以及演示)ios
可是,爲了這篇文章,我從新在 codesandbox 上面製做了一個示例,來專一於這個問題的解決方案。git
首先,咱們將如何使用 URL ?github
咱們將使用 URL 中,? 後面所包含的全部內容,就是所謂的搜索參數。web
搜索參數的 MDN 連接
在本文的上下文中,咱們將只使用一個查詢參數:query
。npm
爲了收集該參數(若是它確實存在於 URL,例如 https://myApp.io?query=javascript
),咱們將會檢查搜索參數。幸運的是,他們能夠在 window
對象中很容易找到。更準確的說,是 winndow.location.search
。axios
所以,當咱們訪問 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。
爲此,我準備了一個簡單的輸入框示例:
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▽ ̄)~*捂嘴偷笑
但願對你有幫助!
譯註:我偷偷改了做者原來的顏文字... 本文已經聯繫原文做者,並受權翻譯,轉載請保留原文連接