你想知道的Redux和React-Router都在这里

1,530 阅读6分钟

Redux 和 React-Router 的入门笔记

写在前面,笔者才疏学浅,写此文章仅做参考!不对之处,敬请海涵!

此篇文章主要说明 Redux 和 React-Router 基本用法。Redux 是全局的状态仓库,它可以解决错综复杂的状态关系,并进行全局的统一管理。React-Router 是 React 的路由库,它可以让你向应用中快速地添加视图和数据流,同时保持页面与URL间的同步。另外,浏览此文之前,强烈建议先浏览——React初学者必看

文章多代码,请耐心阅读,笔者也是为了更好理解。

Redux

Redux 是一个用来管理管理数据状态和 UI 状态的 JavaScript 应用工具。随着 JavaScript 单页应用(SPA)开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state(状态),Redux 就是降低管理难度的。(Redux 支持 React,Angular、jQuery 甚至纯 JavaScript)

Redux 工作流程

为了方便理解,我自己画了一张图,如下:

React Components 是一个借书者,他要去向图书管理员借书,图书管理员在图书馆内,为了方便查找,通过 Reducers 图书管理软件找到具体位置后,返回给借书者。

Reducers 是自发的,只要提供了 Action 给 Store 就是自动查找

Use Redux

  • 安装
npm install --save redux
  • 使用
// 在src目录下创建一个store文件夹,然后在文件夹下创建一个index.js文件
// index.js就是整个项目的store文件
import { createStore } from 'redux'  // 引入createStore方法
const store = createStore()          // 创建数据存储仓库
export default store                 //暴露出去

// 在同级目录新建 reducer.js
const defaultState = {
  inputValue : 'Write Something',
  list:[
      '锻炼身体',
      '坚持阅读'
  ]
}
export default (state = defaultState,action)=>{  //就是一个方法函数
    // Reducer里只能接收state,不能改变state
    return state
}
  • 工具
Redux DevTools 工具

// 修改 store/index.js 文件
// 在 createStore 加入第二个参数,如下,你就可以在控制台查看数据参数了
import { createStore } from 'redux'  //  引入createStore方法
import reducer from './reducer'    
const store = createStore(reducer,
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()) // 创建数据存储仓库
export default store   //暴露出去

案例

安装 ant-design

为了更容易理解,笔者做了一个优化todolist的案例。首先安装ant-design:

npm install antd --save

如果没有 demo 项目

// 先安装脚手架工具
npm install -g create-react-app
// 再安装项目
 D:  //进入D盘
 create-react-app demo // 用脚手架创建React项目
 cd demo  // 等项目创建完成后,进入项目目录
 npm start  // 预览项目

修改组件

新创建一个组件,并且引入 ant 组件库中的 Input、button 和 list。

import React, { Component } from 'react';
import 'antd/dist/antd.css' // 导入样式
import { Input , Button , List } from 'antd'

const data=[
    '早8:30开晨会,分配今天的开发工作',
    '早9点和项目经理作开发需求讨论会',
    '晚5:30对今日代码进行review'
]

class TodoList extends Component {
    render() { 
        return ( 
            <div style={{margin:'10px'}}>
                <div>
                    <Input placeholder='write' style={{ width:'250px', marginRight:'10px'}}/>
                    <Button type="primary">增加</Button>
                </div>
                <div style={{margin:'10px',width:'300px'}}>
                    <List
                        bordered
                        dataSource={data}
                        renderItem={item=>(<List.Item>{item}</List.Item>)}
                    />    
                </div>
            </div>
         );
    }
}
export default TodoList;

到此,可以先验证一下是否可以运行。运行成功后,引入 store。

import store from '../store/index';

使用 store

在 constructor 查看 store 中的数据(之前给了默认值)。

constructor(props) {
  super(props);
  //关键代码-----------start
  this.state = store.getState();
  //关键代码-----------end
  console.log(this.state);
}

修改组件中的代码,改成使用 store 中的数据

<div>
  <Input
    placeholder={this.state.inputValue}
    style={{ width: '250px', marginRight: '10px' }}
    onChange={this.onChangeInputValue}
    value={this.state.inputValue}
  />
  <Button type="primary" shape="round">
    增加
  </Button>
</div>
<div style={{ margin: '10px', width: '300px' }}>
  <List
    bordered
    dataSource={this.state.list}
    renderItem={(item) => <List.Item>{item}</List.Item>}
  />
</div>

onChangeInputValue(e) {
  const action = {
    type: 'changeInput', // 自己定义的名称
    value: e.target.value,
  };
  store.dispatch(action); // action 就创建好了,但是要通过 dispatch() 方法传递给 store
}

定义 onChangeInputValue 方法,提交 action 通过 dispatch() 方法传递给 store,store 有自动推送策略,交给 reducer 处理。

修改 reducer

// state: 指的是原始仓库里的状态。
// action: 指的是action新传递的状态。
export default (state = defaultState,action)=>{  //就是一个方法函数
    // Reducer里只能接收state,不能改变state。
    // 先判断 方法名 是不是你传过来的
    if(action.type === 'changeInput'){
      // 再定义一个局部变量
      let newState = JSON.parse(JSON.stringify(state)) // 深度拷贝 state 对象
      newState.inputValue = action.value
      return newState
    }
    return state
}

组件订阅

组件订阅 Redux 的状态,同时改变组件中的值;

constructor(props) {
  super(props);
  this.state = store.getState();
  this.storeChange = this.storeChange.bind(this); // 用来改变 inputValue 值
  store.subscribe(this.storeChange); //订阅 Redux 的状态
}

storeChange() {
  this.setState(store.getState());
}

取消订阅

// 组件卸载,移除时调用该函数,一般取消,清理已注册的订阅,定时器的清理,取消网络请求,在这里面操作
componentWillUnmount() {
    store.unsubscribe(this.storeChange); // 取消订阅,清理已注册的监听
}

Redux 小结

  • store 是唯一的,只能有一个仓库
  • reducer 不能直接改变 store 中的值,所以会定义一个局部变量接收后,再返回
  • store 有自动推送策略,它会交给 reducer 处理

流程:在组件中 提交 action 再通过 dispatch() 方法传递给 store;然后 store 会自动推送给 reducer,再 reducer 方法中修改好返回;最后在组件中订阅 Redux 的状态,同时修改组件中的值;

React-Router

Use React-Router

  • 安装
npm install --save react-router-dom
  • 使用
// index.js
// 首先我们改写src文件目录下的index.js代码
import React from 'react';
import ReactDOM from 'react-dom'
import AppRouter from './AppRouter'

ReactDOM.render(<AppRouter/>,document.getElementById('root')   
// AppRouter.js
// 现在的AppRouter组件是没有的,我们可以在src目录下建立一个AppRouter.js文件
import React from 'react';
// 主要用到的 react-router-dom 包 BrowserRouter Route Link
import {  BrowserRouter as Router, Route, Link } from 'react-router-dom';

import Index from './pages/Index'; // 组件 Index
import List from './pages/List';
import Home from './pages/Home';

function AppRouter() {
  return (
    <Router>
      <ul>
        <li>
          <Link to="/">首页</Link>
        </li>
        <li>
          <Link to="/list/123">列表</Link>
        </li>
      </ul>
      {/* exact:精确匹配 一定是 / 才会匹配 不能是 /123 之类 */}
      <Route path="/" exact component={Index}></Route>
      <Route path="/list/:id" component={List}></Route>
      <Route path="/home/" component={Home} />
    </Router>
  );
}

export default AppRouter;

看完代码,想必你已经了解了路由的基本使用,再看如何实现嵌套路由。

嵌套路由

常见的后台管理都会用到嵌套路由,项目目录结构:

- src
|--page
   |--videos
   |--workPlace
   |--Index.js // 组件文件名一定要大写
   |--Videos.js
   |--Workplace.js
|--index.js
|--AppRouter.js

page 下面有两个目录和三个文件;目录下放置二级路由组件;

AppRouter.js

import React from 'react'; // imr
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import './index.css';
import Index from './page/Index';
import Videos from './page/Videos';
import Workplace from './page/Workplace';
function AppRouter() {
  return (
    <Router>
      <div className="mainDiv">
        <div className="leftNav">
          <h3>一级导航</h3>
          <ul>
            <li>
              <Link to="/">博客首页</Link>
            </li>
            <li>
              <Link to="/videos/">视频教程</Link>
            </li>
            <li>
              <Link to="/workplace/">职场技能</Link>
            </li>
          </ul>
        </div>
        <div className="rightMain">
           <Route path="/" exact component={Index}></Route>
          <Route path="/videos/" component={Videos}></Route>
          <Route path="/workplace/" component={Workplace}></Route> 
        </div>
      </div>
    </Router>
  );
}

export default AppRouter;

这样就配置好了路由信息,需要注意 Route 标签 path 路径是小写,而 component 属性后面是大写;

Videos.js

import React from 'react';
import { Route , Link } from 'react-router-dom'
import  Angular from './videos/Angular'
import  ReactJs from './videos/ReactJs'
import  Vue from './videos/Vue'

function Video(){
  return (
    <div>
        <div className="topNav">
            <ul>
                <li><Link to="/videos/angular">Angular教程</Link></li>
                <li><Link to="/videos/vue">Vue教程</Link></li>
                <li><Link to="/videos/reactJs">React教程</Link></li>
            </ul>
        </div>
        <div className="videoContent">
            <div><h3>视频内容</h3></div>
            <Route path="/videos/angular/"   component={Angular} />
            <Route path="/videos/vue/"   component={Vue} />
            <Route path="/videos/reactJs/"   component={ReactJs} />
        </div>
    </div>
  )
}

export default  Video

ReactJS.JS

import React, { Component } from 'react';

class ReactJs extends Component {
  constructor(props) {
    super(props);
    this.state = {  }
  }
  render() { 
    return ( <h1>我是 React </h1> );
  }
}
 
export default ReactJs;

动态获取路由

为了方便理解,直接附上完整代码,其中 routeConfig 就是从后端获取到的权限菜单列表

import React from 'react'; // imr
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import './index.css';
import Index from './pages/Index';
import Videos from './pages/Videos';
import Workplace from './pages/Workplace';
function AppRouter() {
  let routeConfig = [
    { path: '/', title: '博客首页', exact: true, component: Index },
    { path: '/videos/', title: '视频教程', exact: false, component: Videos },
    {
      path: '/workplace/',
      title: '职场技能',
      exact: false,
      component: Workplace,
    },
  ];
  return (
    <Router>
      <div className="mainDiv">
        <div className="leftNav">
          <h3>一级导航</h3>
          <ul>
            {/* 切记:一定要在js代码外面包裹一层{} */}
            {routeConfig.map((item, index) => {
              return (
                <li>
                  <Link to={item.path}>{item.title}</Link>
                </li>
              );
            })}
          </ul>
          {/* <ul>
            <li>
              <Link to="/">博客首页</Link>
            </li>
            <li>
              <Link to="/videos/">视频教程</Link>
            </li>
            <li>
              <Link to="/workplace/">职场技能</Link>
            </li>
          </ul> */}
        </div>
        <div className="rightMain">
          {routeConfig.map((item, index) => {
            return (
              <Route
                path={item.path}
                exact={item.exact}
                component={item.component}
              ></Route>
            );
          })}
          {/* <Route path="/" exact component={Index}></Route>
          <Route path="/videos/" component={Videos}></Route>
          <Route path="/workplace/" component={Workplace}></Route> */}
        </div>
      </div>
    </Router>
  );
}

export default AppRouter;

Ending

最后,本文只是学习笔记,也是笔者通过看技术胖的视频学到的一些基础知识。坚持很难,但是日复一日的坚持更难!但我相信,只要努力了就会有结果,并且努力的意义在于放眼望去全是你喜欢的人和事物。