一、何为 Redux
Redux 是 JavaScript 应用的状态容器,提供可预测的状态管理。你可以把它想象成 Vue 中的 vuex。
当遇到如下问题时,建议开始使用 Redux:
- 你有很多数据随时间而变化
- 你希望状态有一个唯一确定的来源
- 你发现将所有状态放在顶层组件中管理已不可维护
注意:Redux 并不只为 react 应用提供状态管理, 它还支持其它的框架。除了 Redux 之外,还有 Flux(最早)、Mobx 等状态管理工具。
二、为什么 React 要用 Redux
React 是 DOM 的一个抽象层(UI 库),并不是 Web 应用的完整解决方案。因此 react 在涉及到数据的处理以及组件之间的通信时会比较复杂。
对于大型的复杂应用来说,只用 React,是比较吃力的。因此,诞生了 Flux、Redux、Mobx 等状态管理工具。
那么使用 Redux 有哪些优势呢?
- 集中式存储和管理应用的状态
- 处理组件通讯问题时,无视组件之间的层级关系
- 简化大型复杂应用中组件之间的通讯问题
- 数据流清晰,易于定位 Bug
三、Redux 的核心概念
核心概念:store、action、reducer
1.store
- 定义:仓库,Redux 的核心
- 作用:整合 action 和 reducer
- 特点:
- 一个应用只有一个 store
- 维护应用的状态,获取状态:
store.getState() - 创建 store 时接收 reducer 作为参数:
const store = createStore(reducer) - 发起状态更新时,需要分发 action:
store.dispatch(action)
- 其他 API:
- 订阅(监听)状态变化:
const unSubscribe = store.subscribe(() => {}) - 取消订阅状态变化:
unSubscribe()
- 订阅(监听)状态变化:
2.reducer
- 定义:一个纯函数
- 作用:
- 初始化状态
- 修改状态
- 根据传入的旧状态和 action,返回新状态
3.action
- 定义:只描述有事情发生了这一事实,不描述如何更新 state。
- type: 标识属性,值是字符串。多个 type 用 action 分开
- payload:数据属性,可选。表示本次动作携带的数据
- 作用:
- 只描述做什么
- JS 对象,必须带有
type属性,用于区分动作的类型 - 根据功能的不同,可以携带额外的数据,配合该数据来完成相应功能
四、redux 使用 小 demo
第一步: 基本目录结构说明
├── src
├── pages.js
| ├── 模块1.js # 模块1组件
| └── 模块2.js # 模块2组件
├── store # redux目录,一般约定叫 store
| ├── actions # 异步请求函数在这里
| ├── action1.js # 模块1的相关 action creator
| ├── action2.js # 模块2的相关 action creator
| └── reducers # 多个模块的 reducer
| ├── reducer1.js # 模块1的 reducer
| ├── reducer2.js # 模块2的 reducer
| └── index.js # reducer 的整体入口,会导入 reducer1, reducer2
| ├── actionTypes.js # 定义并导出 action.type 的类型
| └── index.js # 定义并导出 store, 其中会导入 reducer
├── index.js # 项目的入口文件,会导入并渲染 App.js
└── App.js # 根组件,引入模块1和模块2组件
第二步: 基本数据建设
1. 下四个包
npm i redux react-redux redux-thunk redux-devtools-extension
2. store/index.js
法1:不使用 redux - thunk 中间件
import { createStore } from 'redux'
import reducer from './reducers'
import { composeWithDevTools } from 'redux-devtools-extension'
const store = createStore(reducer, composeWithDevTools())
export default store
法2:使用 redux - thunk 中间件
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducers'
const middlewares = composeWithDevTools(applyMiddleware(thunk))
const store = createStore(rootReducer, middlewares)
export default store
3. src/index.js
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
4. store/reducers/login.js
const initValue = {
token: ''
}
export default function login(state = initValue, action) {
return state
}
5. store/reducers/index.js
import { combineReducers } from 'redux'
import login from './login'
const rootReducer = combineReducers({
login
})
export default rootReducer
6. 测试初始效果
第三步:实现登录
1. 点击登录触发 dipatch
import { Card, Form, Input, Checkbox, Button } from 'antd'
import logo from '@/assets/logo.png'
import styles from './index.module.scss'
import { useDispatch } from 'react-redux'
import {loginHandler} from '@/store/actions/login'
const Login = () => {
// ◆校验规则
const mobilePattern = /^(0|86|17951)?(13[0-9]|15[012356789]|166|17[3678]|18[0-9]|14[57])[0-9]{8}$/
const loginRules = {
// 1.手机
mobile: [
{ pattern: mobilePattern, message: '手机号码格式不对', validateTrigger: 'onBlur' },
{ required: true, message: '请输入手机号' }
],
// 2.验证码
code: [
{ len: 6, message: '验证码6个字符', validateTrigger: 'onBlur' },
{ required: true, message: '请输入验证码' }
],
// 3.协议(自定义)
agree: [{
validator: function (rule, value) {
if (value) {
return Promise.resolve()
} else {
return Promise.reject(new Error('请阅读并同意协议'))
}
}
}]
}
// ◆表单默认值
const initialValues={
agree: true,
mobile: '13811111111',
code: '246810'
}
// ◆点击登录
const dispatch=useDispatch()
const onFinish = (values) => {
console.log(values, 'values')
// dispatch
dispatch(loginHandler(values))
}
return (
<div className={styles.root}>
<Card className="login-container">
<img className="login-logo" src={logo} alt="" />
{/* 登录表单 */}
<Form
name="basic"
initialValues={initialValues}
onFinish={onFinish}
autoComplete="off"
size='large'
validateTrigger={['onBlur', 'onChange']}
>
<Form.Item name="mobile" rules={loginRules.mobile}>
<Input placeholder="请输入手机号" />
</Form.Item>
<Form.Item name="code" rules={loginRules.code}>
<Input placeholder="请输入验证码" maxLength={6} />
</Form.Item>
<Form.Item name="agree" valuePropName="checked" rules={loginRules.agree} >
<Checkbox className="login-checkbox-label" >
我已阅读并同意「用户协议」和「隐私条款」
</Checkbox>
</Form.Item>
<Form.Item>
<Button type="primary" htmlType="submit" block>登录
</Button>
</Form.Item>
</Form>
</Card>
</div>
)
}
export default Login
2. actions/login.js 封装登录的函数,return 异步请求函数
import request from '@/utils/request'
import { LOGIN } from '@/store/actionTypes'
export const loginHandler = (payload) => {
return async (dispatch) => {
// 1.发请求
const res = await request({
method: 'post',
url: '/authorizations',
data: payload
})
console.log(res);
// 2.dispatch
dispatch({
type: LOGIN,
payload: res.data.token
})
}
}
3. actionType.js 中定义 action.type 的类型并导出
export const LOGIN='LOGIN'
4.utils/request.js 添加请求和响应拦截器
// yarn add axios
import axios from 'axios'
const instance = axios.create({
baseURL: '基地址写在这里',
timeout: 5000, // 最长等待时间5秒。如果一个请求超过了5秒还没有返回,就认定失败
})
// 请求拦截器
instance.interceptors.request.use(
function (config) {
return config
},
function (error) {
return Promise.reject(error)
}
)
// 响应拦截器
instance.interceptors.response.use(
function (response) {
return response.data
},
function (error) {
return Promise.reject(error)
}
)
export default instance
5. reducers/login.js 匹配 action.type,修改状态
import { LOGIN } from '@/store/actionTypes'
const initValue = {
token: ''
}
export default function login(state = initValue, action) {
switch (action.type) {
case LOGIN:
return { ...state, token: action.payload }
default:
return state
}
}
6. 点击登录,测试 token 值
第四步:封装 storage 工具(额外)
1. utils/storage.js 统一封装 storage工具
// ◆封装本地存储的操作
const TOKEN_KEY = 'linwanxia_lalala'
export function getToken() {
return localStorage.getItem(TOKEN_KEY)
}
export function setToken(token) {
localStorage.setItem(TOKEN_KEY, token)
}
export function removeToken() {
localStorage.removeItem(TOKEN_KEY)
}
export function hasToken() {
return !!getToken()
}
2. actions/login.js 补充 存储 token
import request from '@/utils/request'
import { LOGIN } from '@/store/actionTypes'
// ◆导入 setToken 函数
import { setToken } from '@/utils/storage'
export const loginHandler = (payload) => {
return async (dispatch) => {
// 1.发请求
const res = await request({
method: 'post',
url: '/authorizations',
data: payload
})
console.log(res);
// ◆3.存储到本地
setToken(res.data.token)
// 2.dispatch
dispatch({
type: LOGIN,
payload: res.data.token
})
}
}
3.utils/request.js 补充设置全局请求头
// 省略其他。。。
import { getToken } from './storage'
// ◆请求拦截器
instance.interceptors.request.use(
function (config) {
// 在发请求之前做什么
// 1.获取 token
const token = getToken()
// 2.添加 token
if (token) {
// 设置全局请求头
config.headers.Authorization = `Bearer ${token}`
}
return config
},
function (error) {
return Promise.reject(error)
}
)