测试react-router v4以及redux并实现store持久化
yarn add react-router-dom --save
yarn start
yarn build
React Router中有三种类型的组件:路由器组件,路由匹配组件和导航组件
对于Web项目,react-router-dom
提供<BrowserRouter>
和<HashRouter>
路由器。
两者区别:
BrowserRouter
地址栏显示方式:http://localhost:8080/abc/def
,HashRouter
地址栏显示方式:http://localhost:8080/#/abc/def
<BrowserRouter>
需要server
端的配合,每一次跳转页面都向服务器发送一次请求,服务器需要进行处理<HashRouter>
不会向服务器发起请求,会找到相对于的模块然后进行渲染<HashRouter>
就好<BrowserRouter>
使用:
需要在服务器接受路由,然后返回文件(以node后台为例)
app.get('*',(request,response)=>{
response.sendFile(path.resolve(__dirname,'../index.html'))
})
<HashRouter>
使用:
import { HashRouter as Router, Route } from 'react-router-dom'
import Home from './views/home'
<Router>
<Route path='/' component={Home}/>
</Router>
<Route>
和<Switch>
<Switch>
可对子元素<Route>
进行分组,并仅渲染与当前位置匹配的第一个元素
<Route>
如果匹配到对应的path
将会渲染对应组件,如果没有匹配到,则返回null
,如果不设置path
属性,将会一直渲染
使用:
import About from './views/about'
import Home from './views/home'
import Services from './views/services'
public render():JSX.Element {
return (
<Router>
<Switch>
{/* 当路由为 " / " 时 */}
{/* Home组件将会被渲染 */}
<Route path='/' component={Home}/>
{/* About不会渲染,返回null */}
<Route path='/about' component={About}/>
{/*上面都不匹配将会渲染Services组件 */}
<Route component={Services}/>
</Switch>
</Router>
)
}
component
,render
,和children
。使用:
import About from './views/about'
import Home from './views/home'
import Services from './views/services'
const AboutCom = (props:any) => {
return (
<About />
)
}
class App extends React.Component {
public render():JSX.Element {
return (
<Router>
<Switch>
{/* 使用component渲染 */}
<Route path='/home' component={Home}/>
{/* 使用render渲染 */}
<Route path='/about'
render={AboutCom}/>
{/* 使用children渲染 */}
<Route children={AboutCom}/>
</Switch>
</Router>
)
}
}
<Link>
导航这个组件主要用于导航,它将会找到相匹配的路由,然后进行渲染,以下为测试结果:
App.tsx
<Router>
<Switch>
<Route path='/' exact={true} component={Home} />
<Route path='/linkTest' component={LinkTest} />>
</Switch>
</Router>
Home.tsx
<div>
<p>这是首页</p>
<Link to='/linkTest'>点击这个Link跳转到Link组件</Link>
</div>
默认页面是Home,点击Home页面中Link跳转到LinkTest页面,因为这是在App.tsx中定义的,所以将会重新打开一个新的页面
将导航到新的位置,且新位置会覆盖历史堆栈中的位置
<Router getUserConfirmation={getConfirmation}>
<Switch>
<Route path='/' exact={true} component={Home}/>
<Route path='/home' component={Home}/>
<Route path='/about'
render={AboutCom}/>
{/* 当地址为:ip::3000/#/anywhere都将跳转到Home页面 */}
<Redirect to='/'/>
</Switch>
</Router>
<Route path='/roster'/>
// 当路径名为'/'时, path不匹配
// 当路径名为'/roster'或'/roster/2'时, path匹配
// 当你只想匹配'/roster'时,你需要使用"exact"参数, 路由仅匹配'/roster'而不会匹配'/roster/2'
<Route exact={true} path='/roster'/>
在以前的版本中可以使用以下:
<Route component={App}>
<Route path="/" components={Home} />
<Route path="users" components={Users}>
<Route path="users/:userId" component={Profile} />
</Route>
</Route>
但是在4.0版本之后就和之前的版本不一样了,详见;react-router官网
<Route path='/user/:id' component={User} />
string
:<Link to={'/user/2'}>跳转传递字符串</Link>
Object
:<Link to={{ pathname:'/user', search: '?sort=name',hash: '#the-hash',state: { fromDashboard: true }}}>跳转传递对象</Link>
User
页面获取
string
时获取方式:this.props.match.params.id
Object
时获取方式: this.props.location
1.这里的
id
和路由中的/user/:id
一样,如果路由为/user/:userId
,则获取this.props.match.params.userId
;2.如果参数是对象,则存在this.props.location
中
yarn add react-loadable
在router/index.tsx
引入使用:
import * as React from 'react'
import * as Loadable from 'react-loadable'
interface IRoute {
component: Promise<React.ComponentClass<any> | React.StatelessComponent<any> | { default: React.ComponentType<any> }>,
path: string
}
export const route:IRoute[] = [
{
component: import('../views/Support'),
path: '/support'
},
{
component: import('../views/About'),
path: '/about'
},
{
component: import('../views/Services'),
path: '/services'
}
]
function Loading(props: any) {
console.log(props)
if (props.error) {
return <div>Error!</div>
} else if (props.timedOut) {
return <div>Taking a long time...</div>
} else if (props.pastDelay) {
return <div>loading</div>
} else {
return <div>loading</div>
}
}
export const loadable = (component: Promise<React.ComponentClass<any> | React.StatelessComponent<any> | { default: React.ComponentType<any> }>) => {
return Loadable({
delay: 200,
loader: () => component,
loading: Loading,
timeout: 10000,
})
}
在定义路由的地方调用:
import * as React from 'react'
import { Route, Switch } from 'react-router-dom'
import Nav from '../../component/nav'
import { loadable, route } from '../../routes'
interface IRouteItem {
component: Promise<React.ComponentClass<any> | React.StatelessComponent<any> | { default: React.ComponentType<any> }>,
path: string
}
export default class Home extends React.Component {
public render ():JSX.Element {
return (
<div>
<Nav />
<Switch>
{route.map((item:IRouteItem, index:number) => {
return (
<Route path={item.path} component={loadable(item.component)} key={index}/>
)
})}
</Switch>
</div>
)
}
}
这是不可逆的操作,它将
create-react-app
的配置反编译到当前项目,从而完全取得 webpack 文件的控制权,无法回到原来的项目版本!自己在使用的时候,它提示让我提交了代码再使用这命令,应该是想让我保存当前版本吧。-.-
yarn eject
这个命令详见:这里
npm install less less-loader --save-dev
webpack.config.dev.js
和webpack.config.prod.js
文件(做相同配置)css-loader
所在位置css
的test
正则 为/\.(css|less)$/,
less-loader
{
loader: require.resolve('less-loader'),
options: {
importLoaders: 1
}
}
exclude
,在末尾添加/\.(css|less)$/
Redux
是 JavaScript
状态容器,它提供对状态的统一管理Redux
除了和 React
一起用外,还支持其它界面库,如果需要在React
中使用,需要单独安装react-redux
react-redux
是一个能过够让Redux
在React
项目中使用的第三方库Redux
需要关注的有:Action,Reducer,Store
Action
主要用来管理一些分发出去的动作。
Action
本质上是一个普通对象,action
内必须使用一个字符串类型的 type
字段来表示将要执行的动作,当应用规模越来越大时,建议使用单独的模块或文件来存放 action
。如下:
redux/action/index.tsx
// 为了后期项目的可维护,把所有的action都写在单独文件
const ACTION = {
CHANGEVALUE: (obj:any) => {
return {
type: 'CHANGEVALUE',
value: obj.value
}
},
DECREASE: {
type: 'DECREASE'
},
INCREASE: {
type: 'INCREASE'
}
}
export default ACTION
Reducer
主要指定了一些如何改变store
中状态的方法:如下
redux/reducers/index.tsx
interface IInitState {
count: number
}
interface IActionType {
type: string,
value: string
}
export default function reducers (state: IInitState , action: IActionType) {
// 这里是根据传入动作的type,来对Redux中的state进行状态值修改
switch (action.type) {
case 'INCREASE':
return { count: ++state.count }
break
case 'DECREASE':
return { count: --state.count }
break
case 'CHANGEVALUE':
return {count: action.value}
break
default:
return { count: state.count }
}
}
Store
就是将Action
和Reducer
联系起来,使得可以通过Action
来修改State
主要API:
redux/store/index.tsx
import { createStore } from 'redux'
import reducer from '../reducers'
export const initState = {
count: 0
}
// createStore 方法详见Redux API文档:http://www.redux.org.cn/docs/api/createStore.html
const store = createStore(reducer, initState)
export default store
现在
redux
的工作已经完成了。我们可以在react文件中直接引入:import store from '../redux/store/index.tsx'
,然后使用Store
的api
进行操作,但是这样并不会触发react
组件的更新,因为他们之间缺少了映射关系,这时候就需要用到react-redux
这个第三方库
Redux
和react-redux
是不同的库,如果我们想要在react
中使用,我们需要用到react-redux
这个库,主要作用是将我们项目的状态集成到Redux
中去管理
需要关注的有:connect, Provider
connect
方法,顾名思义,主要将容器组件和redux进行连接
javascript
// 这里是把component这个组件进行连接,连接后可在props上查看映射关系
const appContainer = connect(mapStateToProps, mapDispatchToProps)(component)
mapStateToProps
和 mapDispatchToProps
,这两个参数的类型都是函数,具体作用如下:mapStateToProps
:将Redux
的state
映射到组件的props
上mapDispatchToProps
:将Redux
的action
映射到组件的props
上,如下:redux/index.tsx
:
import { connect } from 'react-redux'
import appContainer from '../views/Appcontainer'
import ACTION from './action'
function mapStateToProps(state: any) {
// 这里将会把 strore 中的initState映射到组件的props上
return {
count: state.count
}
}
function mapDispatchToProps(dispatch: (obj: any) => void) {
return {
// CHANGEVALUE, DECREASE, INCREASE这些函数名会映射到被连接的组件props上
CHANGEVALUE: (obj:any) => {
// 当连接redux的组件调用props上的CHANGEVALUE方法时,此处将分发对应的Action,Action在redux/action/index.tsx中维护
// 找到reducers中相匹配的action,然后执行定义的方法
dispatch(ACTION.CHANGEVALUE(obj))
},
DECREASE: () => {
dispatch(ACTION.DECREASE)
},
INCREASE: () => {
dispatch(ACTION.INCREASE)
}
}
}
// 此处将连接好的组件导出
export const AppContainer = connect(mapStateToProps, mapDispatchToProps)(appContainer)
如果需要现在使用导出的
appContainer
组件,还需要用到react-redux
提供的Provider
组件
它主要的功能有:
App.tsx
import * as React from 'react'
import { Provider } from 'react-redux'
import './App.css'
import { AppContainer } from './redux'
import store from './redux/store'
class App extends React.Component {
public render(): JSX.Element {
return (
<div>
<Provider store={store}>
<AppContainer/>
</Provider>
</div>
)
}
}
export default App
AppContainer.tsx
import * as React from 'react'
import { HashRouter as Router, Route } from 'react-router-dom'
import Nav from '../../component/nav'
import { loadable, route } from '../../routes'
import Home from '../Home'
interface IRouteItem {
component: Promise<React.ComponentClass<any> | React.StatelessComponent<any> | { default: React.ComponentType<any> }>,
path: string
}
export default class Appcontainer extends React.Component {
public render ():JSX.Element {
return (
<Router>
<div>
<Nav />
<Route path='/' exact={true} component={Home}/>
{
route.map((item:IRouteItem, index:number) => {
return (
<Route path={item.path} component={loadable(item.component)} key={index}/>
)
})
}
</div>
</Router>
)
}
}
验证
react-redux
已经将store
映射到Appcontainer
组件,如下:
可以看到
Appcontainer
组件的props已经有了之前在redux/index.tsx
中定义的方法,现在就可以在Appcontainer
中直接使用这些方法来修改store
在编写组件的时候我们为了可维护性,不会把组件都写在一个文件,所以肯定会有很多子组件或者孙子组件。一般来说,我们最外层的父组件用于逻辑处理,而子组件等用来做UI显示。
但是在子孙组件中,获取store
不能如同在Appcontainer
组件中一样通过this.props.xx
获取,现在了解的有三种方法:
Appcontainer
作为父组件,可以通过props
一层一层的把方法和值传递给子组件或者孙子组件A
,在redux/index.tsx
使用connect
方法将A
组件与redux
进行连接,再导出调用,这样子组件也有了和Appcontainer
组件一样的操作store
的方法和值Appcontainer
组件中定义context
上下文,子组件通过this.context.xx.
来获取这里我使用的是第三种方法:context
Appcontainer.tsx
中:
import * as PropTypes from 'prop-types'
import * as React from 'react'
interface IRouteItem {
component: Promise<React.ComponentClass<any> | React.StatelessComponent<any> | { default: React.ComponentType<any> }>,
path: string
}
export default class Appcontainer extends React.Component<any, any>{
// childContextTypes属性,声明给子孙组件提供的属性
public static childContextTypes = {
store: PropTypes.object
}
constructor (props:any) {
super(props)
}
// 这个方法给子组件设置context的值,值为当前组件的props
public getChildContext () {
return {
store: this.props
}
}
public render ():JSX.Element {
return (
...
)
}
}
子组件Counter
中:
import * as PropTypes from 'prop-types'
export default class Counter extends React.Component<any, any> {
// 声明静态属性 contextTypes 才能访问顶层组件定义的context,属性名字也需要和父组件一样,否则访问不到值
public static contextTypes = {
store: PropTypes.object
}
public handleDecreaseCB () {
this.context.store.DECREASE()
}
public render () {
return (
<div className="container">
<div className="container_count">
<DecreaseBtn handleDecrease={this.handleDecreaseCB}/>
{this.props.children}
</div>
</div>
)
}
}
demo效果如下:
redux
持久化效果图中有一个问题,就是刷新后,原来的store变为了0,变成之前初始化state
的值,如果需要让数据持久化,则需要用到一个第三方包:redux-persist
,配置也很简单:
redux-persist
包yarn add redux-persist --save
redux/store/index.tsx
import { createStore } from 'redux'
import * as persist from 'redux-persist'
import storage from 'redux-persist/lib/storage'
import reducer from '../reducers'
const { persistStore, persistReducer } = persist
const persistConfig = {
key: 'root',
storage,
}
const initState = {
count: 0
}
const persistedReducer = persistReducer(persistConfig, reducer)
const store = createStore(persistedReducer, initState)
const persistor = persistStore(store)
export default {
persistor,
store
}
App.tsx
import * as React from 'react'
import { Provider } from 'react-redux'
import { PersistGate } from 'redux-persist/integration/react'
import './App.css'
import { AppContainer } from './redux'
import { persistor, store } from './redux/store'
export default class App extends React.Component {
public render(): JSX.Element {
return (
<div>
<Provider store={store}>
<PersistGate loading='加载中...' persistor={persistor}>
<AppContainer/>
</PersistGate>
</Provider>
</div>
)
}
}
以上方法都是参照官方的例子实现,详见:gitHub,效果如下: