原文: 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▽ ̄)~*捂嘴偷笑
但愿对你有帮助!
译注:我偷偷改了做者原来的颜文字... 本文已经联系原文做者,并受权翻译,转载请保留原文连接