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