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