React技术栈之React-Router
React-RouterReact官方维护的React-Router可以帮我们创建React单页应用。通过管理URL,实现组件的切换和状态的变化。
本文代码可以从这里获取:https://github.com/zhutx/reac...。 SPA在不同“页面”之前切换,但感知不到刷新,只是局部更新,这种看起来多页面而实际只有一个页面的应用,被称为“单页应用”(Single Page Appliaction)。 传统多页面实现方式,每次切换界面,浏览器都会发起一个HTTP请求到服务端,服务端返回HTML,浏览器解析HTML,再根据解析结果继续下载其他比如JavsScript,CSS,图片等资源。 缺点很明显: 路由定义React-Router提供了两个组件来完成路由功能,一个是Router,整个应用只需要一个实例,代表着路由表。另外一个是Route,可以有多个实例,每个Route代表着一个URL的路由规则。 安装react-router依赖包: npm install react-router@3.0.5 --save 创建3个组件页,Home、About、NotFound,代码都一样,只是显示不同。 Home组件,src/component/Home.jsx: import React from 'react'; const Home = () => { return ( <div>Home</div> ); }; export default Home; 定义路由组件,src/routes/index.jsx: import React from 'react'; import { Router,Route,browserHistory } from 'react-router'; import Home from '../component/Home'; import About from '../component/About'; import NotFound from '../component/NotFound'; const Routes = () => { return ( <Router history={browserHistory}> <Route path="home" component={Home} /> <Route path="about" component={About} /> <Route path="*" component={NotFound} /> </Router> ); }; export default Routes; 路由表Router中包含3个Route路由规则:路径home路由到Home组件,路径about路由到About组件,其他路径则统一路由到NotFound组件。由于路由规则按定义的顺序匹配,因此NotFound组件对应的路由规则,必须放最后面。 路由表的history属性,监听浏览器地址栏的变化,并将URL解析成一个地址对象,供路由表去匹配对应的路由规则。 history属性,一共可以设置三种值: 2) browserHistory: $ webpack-dev-server --inline --content-base . --history-api-fallback 3) createMemoryHistory: const history = createMemoryHistory(location) 应用入口引入路由组件,实现路由效果。src/index.jsx: import React from 'react'; import ReactDOM from 'react-dom'; import Routes from './routes'; const app = document.createElement('div'); document.body.appendChild(app); ReactDOM.render(<Routes />,app); 路由链接路由链接常用于应用内栏目导航。React-Router提供Link组件来支持路由链接,避免了使用HTML的<a/>链接元素,因为<a/>链接在点击时默认行为是网页跳转,这并不是SPA应该有的行为。 Link组件产生HTML链接元素,但是对这个链接元素的点击操作并不引起网页跳转,而是被Link截获,把目标路径发送给Router路由表,这样Router就知道匹配哪个路由规则,从而显示对应的组件。 路由链接示例组件,src/component/Nav.jsx: import React from 'react'; import { Link } from 'react-router'; const Nav = () => { return ( <div> <ul> <li><Link to="/home">Home</Link></li> <li><Link to="/about">About</Link></li> </ul> </div> ); }; export default Nav; 注意Link组件的to元素的路径前面有个“/”符号,代表从根路径开始匹配。 有了顶栏Nav组件之后,可以在Home、About和NotFound组件中引用这个Nav组件,但这种修改每个页面文件的方式很笨拙。将来如果要用另外一个组件替换Nav,又要修改每个页面。 React-Router提供的路由嵌套功能,可以很方便的解决这个问题。 路由嵌套我们定义一个新的React组件App,只让这个组件包含Nav组件。 import React from 'react'; import Nav from './Nav'; const App = ({ children }) => { return ( <div> <Nav /> <div>{children}</div> </div> ); }; export default App; 修改路由组件src/routes/index.jsx,增加根路径“/”的路由规则,并嵌套其他路由规则: import React from 'react'; import { Router,browserHistory } from 'react-router'; import App from '../component/App'; import Home from '../component/Home'; import About from '../component/About'; import NotFound from '../component/NotFound'; const Routes = () => { return ( <Router history={browserHistory}> <Route path="/" component={App}> <Route path="home" component={Home} /> <Route path="about" component={About} /> <Route path="*" component={NotFound} /> </Route> </Router> ); }; export default Routes; 这时,假如用户在浏览器中访问http://localhost:8080/home,会根据路径“/home”,匹配到path="/"的Route,再匹配到path="home"的Route。React-Router会渲染外层Route组件,即App组件,但是会把内层Route组件作为children属性传递给外层组件。所以在渲染App组件时,渲染children属性也就把Home组件渲染出来了,最终效果是Nav和Home组件都显示在页面上。 路由嵌套的好处就是每一层Route只决定到这一层的路径,而不是整个路径,非常灵活。例如,我们修改App的Route属性path如下: <Route path="/root" component={App}> 上面的Route规则把App的Route规则改为路径“/root”,那么,访问Home的路径就变成http://localhost:8080/root/home, 而Home、About和NotFound的Route无需做任何修改,因为每个Route只匹配自己这一层的路径,当App已经匹配root部分之后,Home只需要匹配home部分。 默认路由在路径为空时,应用也应该显示有意义的内容,通常对应主页内容。这里,我们希望路径为空时显示Home组件。 <Router history={browserHistory}> <Route path="/" component={App}> <IndexRoute component={Home} /> <Route path="home" component={Home} /> <Route path="about" component={About} /> <Route path="*" component={NotFound} /> </Route> </Router> 注意:IndexRoute是没有path属性的。 路径通配符path属性可以使用通配符。 <Route path="/hello/:name"> // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/hello(/:name)"> // 匹配 /hello // 匹配 /hello/michael // 匹配 /hello/ryan <Route path="/files/*.*"> // 匹配 /files/hello.jpg // 匹配 /files/hello.html <Route path="/files/*"> // 匹配 /files/ // 匹配 /files/a // 匹配 /files/a/b <Route path="/**/*.jpg"> // 匹配 /files/hello.jpg // 匹配 /files/path/to/file.jpg 通配符的规则如下。 path属性也可以使用相对路径(不以/开头),匹配时就会相对于父组件的路径,可以参考上一节的例子。嵌套路由如果想摆脱这个规则,可以使用绝对路由。 <Route path="/comments" ... /> <Route path="/comments" ... /> 上面代码中,路径/comments同时匹配两个规则,第二个规则不会生效。 <Router> <Route path="/:userName/:id" component={UserPage}/> <Route path="/about/me" component={About}/> </Router> 上面代码中,用户访问/about/me时,不会触发第二个路由规则,因为它会匹配/:userName/:id这个规则。因此,带参数的路径一般要写在路由规则的底部。 路由转发React-Router的Redirect组件用于路由的跳转,即用户访问一个路由,会自动跳转到另一个路由。 <Route path="inbox" component={Inbox}> {/* 从 /inbox/messages/:id 跳转到 /messages/:id */} <Redirect from="messages/:id" to="/messages/:id" /> </Route> 现在访问/inbox/messages/5,会自动跳转到/messages/5 根路由重定向React-Router的IndexRedirect组件用于访问根路由的时候,将用户重定向到某个子组件。 <Route path="/" component={App}> <IndexRedirect to="/welcome" /> <Route path="welcome" component={Welcome} /> <Route path="about" component={About} /> </Route> 上面代码中,用户访问根路径时,将自动重定向到子组件welcome。 路由钩子每个路由都有Enter和Leave钩子,用户进入或离开该路由时触发。 <Route path="about" component={About} /> <Route path="inbox" component={Inbox}> <Redirect from="messages/:id" to="/messages/:id" /> </Route> 上面的代码中,如果用户离开/messages/:id,进入/about时,会依次触发以下的钩子。 /messages/:id的onLeave /inbox的onLeave /about的onEnter 下面是一个例子,使用onEnter钩子替代<Redirect>组件。 <Route path="inbox" component={Inbox}> <Route path="messages/:id" onEnter={ ({params},replace) => replace(`/messages/${params.id}`) } /> </Route> onEnter钩子还可以用来做认证。 const requireAuth = (nextState,replace) => { if (!auth.isAdmin()) { // Redirect to Home page if not an Admin replace({ pathname: '/' }) } } export const AdminRoutes = () => { return ( <Route path="/admin" component={Admin} onEnter={requireAuth} /> ) } 下面是一个高级应用,当用户离开一个路径的时候,跳出一个提示框,要求用户确认是否离开。 const Home = withRouter( React.createClass({ componentDidMount() { this.props.router.setRouteLeaveHook( this.props.route,this.routerWillLeave ) },routerWillLeave(nextLocation) { // 返回 false 会继续停留当前页面, // 否则,返回一个字符串,会显示给用户,让其自己决定 if (!this.state.isSaved) return '确认要离开?'; },}) ) 上面代码中,setRouteLeaveHook方法为Leave钩子指定routerWillLeave函数。该方法如果返回false,将阻止路由的切换,否则就返回一个字符串,提示用户决定是否要切换。 表单操作的路由处理Link组件用于正常的用户点击跳转,但是有时还需要表单跳转、点击按钮跳转等操作。这些情况怎么跟React Router对接呢? <form onSubmit={this.handleSubmit}> <input type="text" placeholder="userName"/> <input type="text" placeholder="repo"/> <button type="submit">Go</button> </form> 第一种方法是使用browserHistory.push import { browserHistory } from 'react-router' // ... handleSubmit(event) { event.preventDefault() const userName = event.target.elements[0].value const repo = event.target.elements[1].value const path = `/repos/${userName}/${repo}` browserHistory.push(path) }, 第二种方法是使用context对象。 export default React.createClass({ // ask for `router` from context contextTypes: { router: React.PropTypes.object },handleSubmit(event) { // ... this.context.router.push(path) },}) react-router-redux由于我们依然希望用Redux来管理应用的状态,因此要集成Redux。 安装依赖包: npm install --save redux react-redux react-router-redux 我们在store.js中添加Redux Store的代码: import { createStore,compose,combineReducers } from 'redux'; import { routerReducer } from 'react-router-redux'; const reducer = combineReducers({ routing: routerReducer }); const win = window; const storeEnhancers = compose( (win && win.devToolsExtension) ? win.devToolsExtension() : (f) => f ); const initialState = {}; export default createStore(reducer,initialState,storeEnhancers); 上面定义的Store只是一个例子,并没有添加实际的reducer和初始状态,主要使用了Redux Devtools。 使用React-Redux库的Provider组件,作为数据提供者,Provider必须居于React组件顶层。 import React from 'react'; import ReactDOM from 'react-dom'; import { Provider } from 'react-redux'; import Routes from './routes'; import store from './store'; const app = document.createElement('div'); document.body.appendChild(app); ReactDOM.render( <Provider store={store}> <Routes /> </Provider>,app); 使用React-Router,路由信息存储在浏览器URL上,利用Redux Devtools调试时,无法重现网页之间的切换,因为当前路由作为应用状态根本没体现在Redux的Store上。 为了克服这个缺陷,可以利用react-router-redux库来同步路由信息至Redux的Store。 reducer需要由action驱动,修改传给Router的history变量,让history能够协同URL和Store上的状态。 import {syncHistoryWithStore} from 'react-router-redux'; const history = syncHistoryWithStore(browserHistory,store); react-router-redux库的syncHistoryWithStore方法将React-Router的browserHistory和store关联起来,当浏览器URL变化时,会向store派发action对象,同时监听store的变化,当状态树下routing字段发生变化时,反过来会更新浏览器URL。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |