#### 下载依赖文件 `yarn add react-router-dom --save` #### 运行 `yarn start` #### 打包 `yarn build` #### 一些笔记 #### 基本组件 React Router中有三种类型的组件:路由器组件,路由匹配组件和导航组件 ##### 路由器组件 对于Web项目,`react-router-dom`提供``和``路由器。 两者区别: - 地址栏: `BrowserRouter`地址栏显示方式:` http://localhost:8080/abc/def`,`HashRouter`地址栏显示方式:`http://localhost:8080/#/abc/def` - ``需要`server`端的配合,每一次跳转页面都向服务器发送一次请求,服务器需要进行处理 - ``不会向服务器发起请求,会找到相对于的模块然后进行渲染 - 个人觉得如果是静态页面,使用``就好 ``使用: 需要在服务器接受路由,然后返回文件(以node后台为例) ```javascript app.get('*',(request,response)=>{ response.sendFile(path.resolve(__dirname,'../index.html')) }) ``` ``使用: ```javascript import { HashRouter as Router, Route } from 'react-router-dom' import Home from './views/home' ``` ##### 路由匹配组件``和`` ``可对子元素``进行分组,并仅渲染与当前位置匹配的第一个元素 ``如果匹配到对应的`path`将会渲染对应组件,如果没有匹配到,则返回`null`,如果不设置`path`属性,将会一直渲染 使用: ```javascript import About from './views/about' import Home from './views/home' import Services from './views/services' public render():JSX.Element { return ( {/* 当路由为 " / " 时 */} {/* Home组件将会被渲染 */} {/* About不会渲染,返回null */} {/*上面都不匹配将会渲染Services组件 */} ) } ``` ##### 渲染方式:`component`,`render`,和`children`。 使用: ```javascript import About from './views/about' import Home from './views/home' import Services from './views/services' const AboutCom = (props:any) => { return ( ) } class App extends React.Component { public render():JSX.Element { return ( {/* 使用component渲染 */} {/* 使用render渲染 */} {/* 使用children渲染 */} ) } } ``` ##### ``导航 这个组件主要用于导航,它将会找到相匹配的路由,然后进行渲染,以下为测试结果: `App.tsx` ```javascript > ``` `Home.tsx` ```javascript

这是首页

点击这个Link跳转到Link组件
``` > 默认页面是Home,点击Home页面中Link跳转到LinkTest页面,因为这是在App.tsx中定义的,所以将会重新打开一个新的页面 ##### `重定向 将导航到新的位置,且新位置会覆盖历史堆栈中的位置 ```javascript {/* 当地址为:ip::3000/#/anywhere都将跳转到Home页面 */} ``` #### 路径(path) ```javascript // 当路径名为'/'时, path不匹配 // 当路径名为'/roster'或'/roster/2'时, path匹配 // 当你只想匹配'/roster'时,你需要使用"exact"参数, 路由仅匹配'/roster'而不会匹配'/roster/2' ``` #### 嵌套路由 在以前的版本中可以使用以下: ```javascript ``` 但是在4.0版本之后就和之前的版本不一样了,详见;[react-router官网](https://reacttraining.com/react-router/web/example/basic) #### 路由传参 - 配置路由 `` - 跳转传参 - 1.传入`string`:`跳转传递字符串 ` - 2.传入`Object`:`跳转传递对象` - `User`页面获取 - 1.传入`string`时获取方式:`this.props.match.params.id` - 2.传入`Object`时获取方式: `this.props.location` > 1.这里的`id`和路由中的`/user/:id`一样,如果路由为 `/user/:userId`,则获取 `this.props.match.params.userId`;2.如果参数是对象,则存在`this.props.location`中 #### 异步加载方案 ##### 安装Loadable `yarn add react-loadable` ##### 简单封装一次 在`router/index.tsx`引入使用: ```javascript import * as React from 'react' import * as Loadable from 'react-loadable' interface IRoute { component: Promise | React.StatelessComponent | { default: React.ComponentType }>, 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
Error!
} else if (props.timedOut) { return
Taking a long time...
} else if (props.pastDelay) { return
loading
} else { return
loading
} } export const loadable = (component: Promise | React.StatelessComponent | { default: React.ComponentType }>) => { return Loadable({ delay: 200, loader: () => component, loading: Loading, timeout: 10000, }) } ``` ##### 调用 在定义路由的地方调用: ```javascript 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.StatelessComponent | { default: React.ComponentType }>, path: string } export default class Home extends React.Component { public render ():JSX.Element { return (
) } } ``` #### Webpack配置Less css加载器 ##### 1.安装less相关依赖 > 这是不可逆的操作,它将`create-react-app`的配置反编译到当前项目,从而完全取得 webpack 文件的控制权,无法回到原来的项目版本! > 自己在使用的时候,它提示让我提交了代码再使用这命令,应该是想让我保存当前版本吧。-.- `yarn eject` 这个命令详见:[这里](https://www.cnblogs.com/z-one/p/9019786.html) ##### 2.安装less相关依赖 `npm install less less-loader --save-dev` ##### 3.配置`webpack.config.dev.js`和`webpack.config.prod.js`文件(做相同配置) - 找到 `css-loader`所在位置 - 修改`css`的`test`正则 为` /\.(css|less)$/,` - 在use中添加`less-loader` ```javascript { loader: require.resolve('less-loader'), options: { importLoaders: 1 } } ``` - 找到`exclude`,在末尾添加`/\.(css|less)$/` #### 什么是Redux - `Redux` 是 `JavaScript` 状态容器,它提供对状态的统一管理 - `Redux` 除了和 `React` 一起用外,还支持其它界面库,如果需要在`React`中使用,需要单独安装`react-redux` - `react-redux`是一个能过够让`Redux`在`React`项目中使用的第三方库 `Redux`需要关注的有:**Action**,**Reducer**,**Store** ##### Action `Action`主要用来管理一些分发出去的动作。 `Action` 本质上是一个普通对象,`action` 内必须使用一个字符串类型的 `type` 字段来表示将要执行的动作,当应用规模越来越大时,建议使用单独的模块或文件来存放 `action`。如下: `redux/action/index.tsx` ```javascript // 为了后期项目的可维护,把所有的action都写在单独文件 const ACTION = { CHANGEVALUE: (obj:any) => { return { type: 'CHANGEVALUE', value: obj.value } }, DECREASE: { type: 'DECREASE' }, INCREASE: { type: 'INCREASE' } } export default ACTION ``` ##### Reducer `Reducer`主要指定了一些如何改变`store`中状态的方法:如下 `redux/reducers/index.tsx` ```javascript 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 `Store`就是将`Action`和`Reducer`联系起来,使得可以通过`Action`来修改`State`
主要API: - getState(),返回应用当前的 state 树。 - dispatch(action),分发 action,这是触发 state 状态值改变 - subscribe(listener),添加一个变化监听器。每当 dispatch action 的时候就会执行,state 树中的一部分可能已经变化。你可以在回调函数里调用 getState() 来拿到当前 state。如果需要解绑这个变化监听器,执行 subscribe 返回的函数即可。 - replaceReducer(nextReducer),替换 store 当前用来计算 state 的 reducer。 `redux/store/index.tsx` ```javascript 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`这个第三方库 #### react-redux的使用 `Redux`和`react-redux`是不同的库,如果我们想要在`react`中使用,我们需要用到`react-redux`这个库,主要作用是将我们项目的状态集成到`Redux`中去管理
需要关注的有:**connect**, **Provider** ##### connect - `connect`方法,顾名思义,主要将容器组件和redux进行连接 ```javascript // 这里是把component这个组件进行连接,连接后可在props上查看映射关系 const appContainer = connect(mapStateToProps, mapDispatchToProps)(component) ``` - 他有两个参数:`mapStateToProps`和 `mapDispatchToProps`,这两个参数的类型都是函数,具体作用如下: - `mapStateToProps`:将`Redux`的`state`映射到组件的`props`上 - `mapDispatchToProps`:将`Redux`的`action`映射到组件的`props`上,如下: `redux/index.tsx`: ```javascript 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`组件 ##### Provider 它主要的功能有: - 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件 - 接收Redux的store作为props,并通过context传递给子组件 `App.tsx` ```javascript 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 (
) } } export default App ``` `AppContainer.tsx` ```javascript 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.StatelessComponent | { default: React.ComponentType }>, path: string } export default class Appcontainer extends React.Component { public render ():JSX.Element { return (
) } } ``` > 验证`react-redux`已经将`store`映射到`Appcontainer`组件,如下: ![image](http://hujiahua.site:9999/markdown/markdown-react-redux-img/markdown-react-redux-1.png) > 可以看到`Appcontainer`组件的props已经有了之前在`redux/index.tsx`中定义的方法,现在就可以在`Appcontainer`中直接使用这些方法来修改`store` ##### 子孙组件调用 在编写组件的时候我们为了可维护性,不会把组件都写在一个文件,所以肯定会有很多子组件或者孙子组件。一般来说,我们最外层的父组件用于逻辑处理,而子组件等用来做UI显示。
但是在子孙组件中,获取`store`不能如同在`Appcontainer`组件中一样通过`this.props.xx`获取,现在了解的有三种方法: - 1.`Appcontainer`作为父组件,可以通过`props`一层一层的把方法和值传递给子组件或者孙子组件 - 2.假设现在有子组件`A`,在`redux/index.tsx`使用`connect`方法将`A`组件与`redux`进行连接,再导出调用,这样子组件也有了和`Appcontainer`组件一样的操作`store`的方法和值 - 3.在`Appcontainer`组件中定义`context`上下文,子组件通过`this.context.xx.`来获取 这里我使用的是第三种方法:`context` `Appcontainer.tsx`中: ```javascript import * as PropTypes from 'prop-types' import * as React from 'react' interface IRouteItem { component: Promise | React.StatelessComponent | { default: React.ComponentType }>, path: string } export default class Appcontainer extends React.Component{ // 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`中: ```javascript import * as PropTypes from 'prop-types' export default class Counter extends React.Component { // 声明静态属性 contextTypes 才能访问顶层组件定义的context,属性名字也需要和父组件一样,否则访问不到值 public static contextTypes = { store: PropTypes.object } public handleDecreaseCB () { this.context.store.DECREASE() } public render () { return (
{this.props.children}
) } } ``` demo效果如下: ![image](http://hujiahua.site:9999/markdown/markdown-react-redux-img/markdown-react-redux-2.gif) #### `redux`持久化 效果图中有一个问题,就是刷新后,原来的store变为了0,变成之前初始化`state`的值,如果需要让数据持久化,则需要用到一个第三方包:`redux-persist`,配置也很简单: ##### 安装`redux-persist`包 `yarn add redux-persist --save` ##### `redux/store/index.tsx` ```javascript 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` ```javascript 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 (
) } } ``` > 以上方法都是参照官方的例子实现,详见:[gitHub](https://github.com/rt2zz/redux-persist),效果如下: ![image](http://hujiahua.site:9999/markdown/markdown-react-redux-img/markdown-react-redux-3.gif)