使用React + Redux + TypeScript来实现react-router
现在标题已经有点不明确了呢。嗯,按照SEO的角度来说,应该还是保守一点比较好吧。
这是这篇文章的续集。
http://qiita.com/uryyyyyyy/items/41334a810f1501ece87d
※编辑历史
-
- react-router v4に対応
- React16-beta & TS2.4 対応
提出一个问题
我不想使用没有类型检查的语言!(第5次)
这次我们将引入react-router,顺便尝试一下多个reducer的适配(第一次)。
环境
-
- NodeJS 8.2
-
- React 16.0-beta
-
- TypeScript 2.4
-
- webpack
- react-router 4.1
构成
请参考以下链接:
https://github.com/uryyyyyyy/react-redux-sample/tree/react-router
在引入react-router之前
在当前的SPA中,通常的URL会像这样:http://localhost/todo/1。
如果直接组织的话,当访问/todo/1时,会取得位于todo/1目录下的文件,就不再是SPA了。
相反,需要在服务器端进行设置,始终返回根目录的index.html文件。
在之前的文章中,我们已经介绍了express的使用方式,具体如下所示。
const path = require('path');
const express = require('express');
const app = express();
app.use('/dist', express.static('dist'));
app.get('/api/count', (req, res) => {
res.contentType('application/json');
const obj = {"amount": 100};
setTimeout(() => res.json(obj), 500);
//res.status(400).json(obj); //for error testing
});
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(3000, (err) => {
if (err) {
console.log(err);
}
console.log("server start at port 3000")
});
在这里,我们不讨论“express”的写法,但简单来说,
-
- /distの時はdistで返す。
バンドルされたjsやcssを返す
/api/countのときはjsonを作って返す。
それ以外のURLは全部rootのindex.htmlにリダイレクトする。
這是怎麼運作的:
如果我們直接跳轉到/todo/1等頁面,
如果沒有匹配該頁面的URL→讀取根目錄的index.html→讀取dist資料夾下的js檔案→js的react-router會顯示正確的頁面,
這樣就達到了我們想要的效果。
(※我对于将所有页面都返回给index.html是否是最佳选择有些疑问,但根据react-router的问题和rails的SPA支持,似乎都是这样处理的。)
(※顺便说一句,在这种情况下,不管在客户端访问哪个页面,都会返回index.html,所以访问其他资源时必须使用绝对路径。
这是因为,例如访问 /todo/1 这样的页面时,如果index.html中使用相对路径,会导致去查找 /todo 目录中的静态文件。)
React-router的引入
Index.tsx的同义词:C:\项目\src\Index.tsx
先改变index.tsx文件如下。
import * as React from "react";
import * as ReactDOM from "react-dom";
import { Router } from 'react-router';
import store from "./store";
import {Provider} from "react-redux";
import createBrowserHistory from 'history/createBrowserHistory';
import {Routes} from "./Routes";
const history = createBrowserHistory()
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<Routes />
</Router>
</Provider>
, document.getElementById('app')
);
只需要中文翻譯一種選擇:
是 react-redux 的機制,因此忽略不談,可以在 中放入 history(對象為瀏覽器,因此需要對應的物件),以準備就緒。
順便一提,在這裡可以根據不同環境放入不同的 history 物件,例如測試或原生應用的 history 物件,以便於使用。
因为这里的Routes类是我自己定义的,所以下面我们来看看这个。
Routes.tsx 文件
import * as React from 'react';
import { Switch } from 'react-router';
import {Link, Route} from 'react-router-dom';
import Counter from './counter/Container';
import NotFound from './NotFound';
export class Routes extends React.Component<{}, {}> {
render() {
return (
<div>
<h1>React Redux sample</h1>
<li><Link to='/' >Home</Link></li>
<li><Link to='/counter' >Counter</Link></li>
<li><Link to='/counter/papaparam' >Counter with param</Link></li>
<Switch>
<Route exact path='/counter' component={Counter} />
<Route path='/counter/:myParams' component={Counter} />
<Route component={NotFound}/>
</Switch>
</div>
)
}
}
每个页面上半部分的li元素等都以链接的方式排列,以便在任何页面上都可以跳转到各个页面。
在SPA中,如果使用普通的a标签等进行页面跳转,由于需要重新获取HTML,页面会瞬间变成全白,无法实现平滑的过渡。因此,建议使用带有router的Link组件进行页面跳转。
接下来是Switch组件。
在这里,您可以根据URL切换要显示的组件。
例如,如果URL为/counter,则会显示Counter组件。
(在这个例子中,当URL为/时,将显示NotFound页面。)
容器.tsx
import {Counter} from './Counter'
import {connect, MapDispatchToPropsParam, MapStateToPropsParam} from 'react-redux'
import {Dispatch} from 'redux'
import {CounterState, decrementAmount, fetchRequestFinish, fetchRequestStart, incrementAmount} from './module'
import {ReduxAction, ReduxState} from '../store'
import {RouteComponentProps} from 'react-router'
const mapStateToProps: MapStateToPropsParam<{value: CounterState, param?: string}, any> = (state: ReduxState, ownProps: RouteComponentProps<{myParams: string | undefined}>) => {
if (ownProps.match.params.myParams === undefined) {
return {value: state.counter}
}
return {value: state.counter, param: ownProps.match.params.myParams}
}
const mapDispatchToProps: MapDispatchToPropsParam<{actions: ActionDispatcher}, {}> = (dispatch: Dispatch<ReduxAction>) => ({actions: new ActionDispatcher(dispatch)})
export default connect(mapStateToProps, mapDispatchToProps)(Counter)
这里写了比必要的类型信息还要多,所以可能有点难以阅读呢。。
如果想在react-router中传递查询参数等,会从redux的外部(OwnProps)传递过来。在mapStateToProps中,OwnProps是指包含内部的myParams(在路由中指定的变量名)的RouteComponentProps类型对象,所以我们通过ownProps.match.params.myParams获取myParams,并将其传递给组件的props。这部分是关于{value:CounterState,param?:string}的内容。这样一来,Counter组件中将确保通过类型检查传递了一个名为param的变量。
关于考试
当想要对包含路由器的组件(如Link或Switch等)进行单元测试时,直接渲染该组件会导致错误,因为未能注入history对象,而该对象是通过注入的。
在这种情况下,您可以使用MemoryRouter来包围要测试的组件进行测试。
请原生中文化以下内容,仅需要一个选项:
参考链接:https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/MemoryRouter.md#memoryrouter
import { MemoryRouter } from 'react-router'
<MemoryRouter>
<YourComponent />
</MemoryRouter>
总结
我尝试安装了react-router v4。
作为实验,还有专为Redux设计的库,但我认为只要按这种方式使用,应该没有任何问题。
我计划下次基于这个来发布各种库的实现示例。