React、react-router、connected-react-router和TypeScript

首先

通常制作网站时,多数情况下会采用多页面结构。而如果使用React来进行多页面之间的切换,使用react-router是最简便的方式。对我来说,当初刚开始前端开发时,如果有这样的文档就不会遇到那么多问题了,所以这是我做的备忘录。就像往常一样,如果不明白的话,阅读官方文档和错误信息是解决问题的捷径。

前提

https://qiita.com/IgnorantCoder/items/88f13569cbf0a1c5eaa1已经完成。换句话说,假设你已经使用过redux和react-redux。

首先,react-router是什么?

React Router是什么?只要去官方网站看一下,就会有解释。

组件是React强大的声明式编程模型的核心。React Router是一组导航组件,可以与您的应用程序声明式地结合使用。无论您想为Web应用程序设置可书签的URL还是在React Native中进行可组合的导航,React Router都可以在React渲染的地方使用 – 所以您尽管选择!

React Router:React.js的声明式路由(https://reacttraining.com/react-router/)

因为我讨厌英语,所以换成日语的话,

组件是React强大声明式编程的本质。React Router就是一个集合了声明式导航组件的工具。不论是想要在Web应用中使用可书签的URL,还是在React Native中进行导航,只要在React渲染下,React Router就会运作。现在就用它吧!

简单来说,使用它可以在React中创建的页面上通过URL进行路由。

暂时先试着做一下

我打算按照以下构想进行制作。

    • /と/fooと/barの3ページ

 

    • すべてのページに全ページへのリンクが張ってあるが、今表示中のページだけリンクになってない

 

    ページの状態をreduxで管理する

安装必要的模块

我們將以最新版為基礎進行撰寫。

# 環境っぽいもの
npm install --save-dev html-webpack-plugin ts-loader tslint-config-airbnb tslint-loader typescript webpack webpack-cli webpack-serve connect-history-api-fallback koa-connect

# 今回使うライブラリ全部
npm install --save react react-dom react-redux react-router react-router-dom connected-react-router redux

# と、その型定義ファイル
npm install --save-dev @types/react @types/react-dom @types/react-redux @types/react-router @types/react-router-dom 

常规的环境设置文件

“webpack.config.js、tsconfig.json、tslint.json这三个文件对吧。在webpack的配置文件中,我们会写入使webpack-serve启动的代码。但是,实际上这个webpack配置文件还有问题,所以稍后会进行修正。”

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'development',
    entry: './src/index.tsx',
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                enforce: 'pre',
                loader: 'tslint-loader',
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true,
                }
            },
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.json',
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: 'static/js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    server: {
        content: path.resolve(__dirname, 'dist'),
        port: 3000,
    },
    plugins: [
        new htmlWebpackPlugin({
            template: "index.html"
        })
    ]
};
{
  "compilerOptions": {
    "sourceMap": true,
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "lib": ["ES2018", "dom"],
    "moduleResolution": "node",
    "removeComments": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "strictFunctionTypes": false
  }
}
{
  "extends": "tslint-config-airbnb",
  "rules": {
    "no-boolean-literal-compare": false,
    "ter-indent": [true, 4]
  }
}

我试着一笔写下来

由于示例代码仅需手写一遍即可成功构建,因此内容不算很重要。
导航栏是一个共同的显示组件,仅根据路由状况决定显示链接还是纯文本。
在根组件中,将使用createBrowserHistory创建的history注入到ConnectedRouter中,并应用routerMiddleware。
在处理redux部分,只需要为reducer和state提供一个专门用于路由的区域。

import * as React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { RootState } from '../modules';

type OwnProps = { // NavBarに実際表示する情報は外から与える
    data: {
        display: string;
        path: string;
    }[];
};

const mapStateToProps = (state: RootState, ownProps: OwnProps) => {
    return {
        items: ownProps.data.map((e) => {
            return {
                display: e.display,
                to: e.path,
                disabled: state.router.location == null
                    ? false
                    : state.router.location.pathname === e.path, // stateからrouterの状態がとれる
            };
        }),
    };
};

type Props = {
    items: {
        display: string;
        to: string;
        disabled: boolean;
    }[];
};

const component: React.SFC<Props> = (props: Props) => {
    return (
        <ul>
            {
                props.items.map((e) => {
                    return (
                        <li key={e.display}>
                            {
                                e.disabled
                                    ? e.display
                                    : <Link to={e.to}>{e.display}</Link>
                            }
                        </li>
                    );
                })
            }
        </ul>
    );
};

export default connect(mapStateToProps)(component);
import { RouterState } from 'connected-react-router';
import { combineReducers } from 'redux';

export type RootState = {  // 必要なら当然自分用のstateも追加してOK
    router: RouterState,   // Router用の領域
};

export const rootReducer = combineReducers({ // 必要に応じて自分用のreducer追加
} as any);
import * as React from 'react';
import { applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import { Route, Switch } from 'react-router';
import { ConnectedRouter, routerMiddleware } from 'connected-react-router';
import createBrowserHistory from 'history/createBrowserHistory';
import { rootReducer } from './modules';
import Home from './pages/Home';
import Foo from './pages/Foo';
import Bar from './pages/Bar';
import NavBar from './containers/NavBar';

const history = createBrowserHistory();           // Browser historyをとって
const store = createStore(
    connectRouter(history)(rootReducer),
    applyMiddleware(routerMiddleware(history)));  // router用のmiddlewareを適用しておく

const component: React.SFC = () => {
    // ConnectedRouterは1エレメントしかとれないのでRoute群はdivで囲っておく
    // Switchで囲っておくと呼ばれたやつだけrenderingする
    // 共通コンポーネントはSwitchの外側に置いとけば良い

    return (
        <Provider store={store}>
            <ConnectedRouter history={history}>
                <div>
                    <NavBar
                        data={[
                            { display: 'to home', path: '/' },
                            { display: 'to foo', path: '/foo' },
                            { display: 'to bar', path: '/bar' },
                        ]}
                    />        
                    <Switch>
                        <Route exact path={'/'} component={Home}/>
                        <Route exact path={'/foo'} component={Foo}/>
                        <Route exact path={'/bar'} component={Bar}/>
                    </Switch>
                </div>
            </ConnectedRouter>
        </Provider>
    );
};

export default component;

除此之外,我添加的源码只是简单地将组件排列在一起。

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(
    <App/>,
    document.getElementById('root'));
import * as React from 'react';

const component: React.SFC = () => {
    return (
        <div>
            This is Home.
        </div>
    );
};

export default component;
import * as React from 'react';

const component: React.SFC = () => {
    return (
        <div>
            This is Foo.
        </div>
    );
};

export default component;
import * as React from 'react';

const component: React.SFC = () => {
    return (
        <div>
            This is Bar.
        </div>
    );
};

export default component;
<!DOCTYPE html>
<html>
<head>
    <title>Router sample</title>
    <meta charset="utf-8">
</head>
<body>
<div id="root"></div>
</body>
</html>

已经完成了吗…?

只需进行此构建,项目就会完成。

webpack-serve --config webpack.config.js

当我点击链接时,页面应该能够正确地跳转。但是,如果直接在地址栏中输入 http://localhost:3000/foo 等内容,则会显示 “Cannnot GET /foo”,并且无法正常运行。

这是可以理解的,因为当访问 http://localhost:3000/ 时,会加载 http://localhost:3000/index.html,其中会渲染bundle.js。如果突然跳转到 http://localhost:3000/foo,那么需要回退到 http://localhost:3000/。

因此,需要修改webpack.config.js来实现这样的回退。

const path = require('path');
const htmlWebpackPlugin = require('html-webpack-plugin');
const history = require('connect-history-api-fallback');
const convert = require('koa-connect');

module.exports = {
    mode: 'development',
    entry: './src/index.tsx',
    devtool: 'inline-source-map',
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                enforce: 'pre',
                loader: 'tslint-loader',
                exclude: [
                    /node_modules/
                ],
                options: {
                    emitErrors: true,
                }
            },
            {
                test: /\.tsx?$/,
                loader: 'ts-loader',
                exclude: [
                    /node_modules/
                ],
                options: {
                    configFile: 'tsconfig.json',
                }
            }
        ]
    },
    resolve: {
        extensions: [ '.tsx', '.ts', '.js' ]
    },
    output: {
        filename: 'static/js/bundle.js',
        path: path.resolve(__dirname, 'dist')
    },
    serve: {
        content: path.resolve(__dirname, 'dist'),
        port: 3000,
        add: (app, middleware, options) => {
            app.use(convert(history({ index: '/' })));
        }
    }
    plugins: [
        new htmlWebpackPlugin({
            template: "index.html"
        })
    ]
};

没问题。
在将其部署到AWS等平台上时,也需要进行相同的设置,以便在出现404错误时返回index.html。

以下是已完成的代碼庫:
https://github.com/IgnorantCoder/typescript-react-redux-router-sample

总结 jié)

只要你理解了Redux,使用react-router本身的代码量也很少,而且并不太难,因此引入它非常简单。但是,每次版本升级时,接口会发生破坏性变化,因此在查询时会发现旧代码也很多。(我也对这篇文章有些不确定)
然而,目前似乎没有其他有效的选择,所以我认为暂时固定版本并进行引入是个好主意。

bannerAds