使用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设计的库,但我认为只要按这种方式使用,应该没有任何问题。

我计划下次基于这个来发布各种库的实现示例。

广告
将在 10 秒后关闭
bannerAds