1.1 概念
1.1.1 Redux 是什么
- Redux 专注于状态管理,和React解耦(没有逻辑上的联系和依赖,可以分开使用)
- 单一状态,单项数据流
- 核心概念:store, state, action, reducer
1.1.2 主要功能
- 有一个保险箱(
store
),所有人的状态在那里都有记录(state
) - 需要改变的时候,需要告诉
dispatch
要干什么(action
) - 处理变化的
reducer
拿到state
和action
,生成新的 state
1.1.3 工作流程
- 首先通过
reducer
新建store
,随时通过store.getState
获取状态 - 需要状态变更,
store.dispatch
(action) 来修改状态 Reducer
函数接受 state 和 action,返回新的 state,可以用store.subscribe
监听每次修改
![1553935619355](F:\我的学习\My Study\07-React\assets\1553935619355.png)
1.1.4 Redux的使用
1.创建 store
/* store.js */
import { createStore } from 'redux';
const store = createStore()
2. 创建 Action
/* action.js */
const LOGIN = "LOGIN"
export function loginAction (loginData) {
return {
type: LOGIN,
loginData // {status: false, user: 'xiaodingyang', password: '123456'}
}
}
3. 创建 reducer
/* reducer.js */
// state中的默认的数据
let initData = {
msg: []
}
export function login (state = initData, action) {
switch (action.type) {
case LOGIN:
return { ...state, msg: action.loginData }
default:
return {...state}
}
}
4. 将 reducer 给 store
import reducer form './reducer';
import { createStore } from 'redux';
const store = createStore(reducer);
5. 将store和组件关联
ReactDOM.render(
<Provider store={store}><App /></Provider>
, document.getElementById('root'));
- 然后就是使用了,使用会在下面具体展示
1.1.5 store的API
1. store.getState()
-
获取state的状态
console.log(store.getState())
2. store.subscribe()
-
订阅 state 变化
const unsubscribe = store.subscribe(()=>{ console.log(state.getState()) }) unsubscribe() // 执行返回值可取消订阅
3. store.dispatch()
-
提交 action
store.dispatch(loginAction())
4. 最终 store.js
-
我们在main.js中导入store.js,就会执行以下代码
import { createStore } from 'redux'; import { loginAction } from 'action.js'; const store = createStore() console.log(store.getState()) const unsubscribe = store.subscribe(()=>{ console.log(state.getState()) }) unsubscribe() // 执行返回值可取消订阅 store.dispatch(loginAction())
1.2 React-redux
1.2.1 安装
npm i react-redux --save
- 安装完 react-redux 以后,就可以:
- 忘记
subscribe
,记住reducer
,action
和dispatch
React-redux
提供Provider
和connect
两个接口来连接
- 忘记
1.2.2 具体使用
-
Provider
向根组件注入 Store,Provider
组件在应用最外层,传入store
即可,只使用一次 -
connect
连接 React 组件和 Redux 状态层,Connect 负责从外部获取组件需要的参数, Connect 可以用装饰器的方式来写/* index.js */ import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; // 引入redux import { createStore, applyMiddleware } from 'redux'; import { counter } from './index-redux'; import { Provider } from 'react-redux'; const store = createStore(counter) //新建 store ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
/* index-redux */ // 根据老的状态和action生成新的状态 export function counter (state = 0, action) { switch (action.type) { case '加': return state + 1; case '减': return state - 1; default: return state } } export function add () { return { type: '加' } } export function reduce () { return { type: '减' } }
/* App.js */ import React, { Component } from 'react' import { Button } from 'antd-mobile'; import { add, reduce } from './index-redux'; import { connect } from 'react-redux' class App extends Component { render () { let num = this.props.num return ( <div className="App"> <Button type="primary" inline onClick={() => this.props.reduce()}>-</Button> <Button type="warning" inline>{num}</Button> <Button type="primary" inline onClick={() => this.props.add()}>+</Button> </div> ) } } // 将 state 值赋值给 num const mapStateProps = (state)=> { return { num: state } } const actionCreators = { add, reduce } // 使用 connect 连接,传入了两个参数,直接挂在到 props 上面,在页面就可以通过 this.props 访问。而且都不用写 dispatch 了 App = connect(mapStateProps, actionCreators)(App) //将 App 注入 connet 从新生成新的 App export default App;
1.2.3 装饰器优化connect
1. npm run eject
弹出个性化配置
2. 安装插件
npm install babel-plugin-transform-decorators-legacy --save-dev
npm install @babel/plugin-proposal-decorators --save-dev
3. package.json
里 babel
加上plugins
配置
"plugins": [
["@babel/plugin-proposal-decorators", { "legacy": true }],
["@babel/plugin-proposal-class-properties", { "loose" : true }]
]
- 将 connect 改写
4. 修改前
const mapStateProps = (state)=> {
return { num: state }
}
const actionCreators = { add, reduce }
App = connect(mapStateProps, actionCreators)(App)
5. 等价于(修改后)
const mapStateProps = (state)=> {
return { num: state }
}
const actionCreators = { add, reduce }
@connect(mapStateProps, actionCreators)
6. 等价于(修改后)
@connect(
(state) => {
return { num: state } //state 里面属性放到 props
},
{ add, reduce } // state 里面方法放到props, 自动 dispatch
)
7. 注意:
- 如果是 vscode 需要将以下设置修改为 true
"javascript.implicitProjectConfig.experimentalDecorators": true
- 在使用
@connect
的时候一定要这样导出:
@connect(
state => state, //state 里面属性放到 props
{ add, reduce, addAsync } // state 里面方法放到props, 自动 dispatch
)
class App extends Component {
render () {
return (
<div className="App"></div>
)
}
}
export default App;
1.2.4 整个 redux 过程
/* index.js */
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
// 引入redux
import { createStore } from 'redux';
import { counter } from './index-redux';
import { Provider } from 'react-redux';
const store = createStore(counter) //新建 stor
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
/* index-redux */
// 根据老的状态和action生成新的状态 (reducer)
export function counter (state = 0, action) {
switch (action.type) {
case '加':
return state + 1;
case '减':
return state - 1;
default:
return state
}
}
// action
export function add () {
return { type: '加' }
}
export function reduce () {
return { type: '减' }
}
App.js
import React, { Component } from 'react'
import './App.css';
import { Button } from 'antd-mobile';
import { add, reduce } from './index-redux'
import { connect } from 'react-redux'
@connect(
(state) => {
return { num: state }
},
{ add, reduce }
)
class App extends Component {
render () {
let num = this.props.num
return (
<div className="App">
<Button type="primary" inline onClick={() => this.props.reduce()}>-</Button>
<Button type="warning" inline>{num}</Button>
<Button type="primary" inline onClick={() => this.props.add()}>+</Button>
</div>
)
}
}
export default App;
1.2.5 combineReducers 拆分 reducer
- 复杂 redux 应用,多个 reducer,用
combineReducers
合并 - 新建一个
reducer.js
文件
/* reducer.js */
import { Auth } from "./component/LoginComponent/login-redux";
import {Counter } from "./component/CounterComponent/counter-redux";
import { combineReducers } from "redux";
export default combineReducers({Counter, Auth})
- 在
index.js
中引入使用。
import Reducer from './reducer';
//新建 store
const store = createStore(Reducer)
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<Switch>
<Route path="/" component={Home}></Route>
<Route path="/login" component={LoginComponent}></Route>
</Switch>
</BrowserRouter>
<App />
</Provider>
, document.getElementById('root'));
1.2.6 异步处理
- 发起action请求是同步的,当我们发起ajax请求的时候,会立即action而不会等到返回数据以后再action。Redux默认只处理同步,异步任务需要
redux-thunk
中间件 - 使用
applyMiddleware
开启中间件 - Action 可以返回函数,使用 dispatch 提交 action
- 安装:
npm i redux-thunk --save
/* index.js */
import { createStore, applyMiddleware } from 'redux'; // applyMiddleware 开启redux-thunk
import thunk from 'redux-thunk';
const store = createStore(Reducer, applyMiddleware(thunk))
/* index-redux.js */
export function addAsync () {
return dispatch=>{
getData().then(res=>{{
dispatch(add(res)) // 将返回的数据传过去
})
}
}
- es6 简写
/* index-redux.js */
export addAsync = ()=> dispatch => {
getData().then(res=>{{
dispatch(add(res)) // 将返回的数据传过去
})
}
1.2.7 redux-devtools 调试工具
- 新建 store 的时候判断
window.devToolsExtension
- 使用
compose
结合thunk
和window.devToolsExtension
- 调试窗的 redux 选项卡,实时看到 state
import { createStore, applyMiddleware, compose } from 'redux';
const store = createStore(Reducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension : () => { }))
1.3 Redux 进阶
1.1.1 Redux 项目结构组织方式
-
Ducks 方式
-
reducers、action、types、actions 组合到一个文件中,作为独立模块
-
划分模块依据:应用状态 State,而不是界面功能。
-
主要使用
const LOGIN = "LOGIN" const LOGOUT = "LOGOUT" // Reducer let initData = { status: false, user: "肖定阳", password: "123456" } export function login (state = initData, action) { switch (action.type) { case LOGIN: return { ...state, status: true } case LOGOUT: return { ...state, status: false } default: return state } } // Action 在使用的时候就不需要一个一个的导入了 export const actions = { log () { return { type: LOGIN } }, logout () { return { type: LOGOUT } } }
1.1.2 redux的state 设计原则
1. 常见的两种错误
- 以 API 为设计 State 的依据
- 以页面 UI 为设计 State 的依据
2. 设计数据库基本原则
- 数据按照领域分类,存储在不同的表中,不同的表中存储的列数据不能重复
- 表中每一列的数据都依赖于这张表的主键
- 表中除了逐渐以外的其他列,互相之间不能有直接依赖关系
3. 设计 redux 的 State 原则
- 数据按照领域把整个应用的状态按照领域分成若干子 State,子 State 之间不能保存重复的数据
- 表中 State 以键值对的解构存储数据,以纪录的 key/id 作为纪录的索引 ,纪录中的其他字段都依赖于索引
- State 中不能保存可以通过已有数据计算而来的数据,即 State中的字段不相互依赖。
- State 应该尽量扁平化(避免嵌套层级过深)
1.1.6.3 Selector 函数
// selector.js
export const getText = state=>state.text
// 组件中
import selector from './selectors'
const mapStateToProps = state = ({
text: getText(state)
})
- selector 还可以将拿到的 state 进行计算在返回
1.1.6.4 前端状态管理思想
- 状态管理对接口进行二次封装处理
![1554164720007](F:\我的学习\My Study\07-React\assets\1554164720007.png)
- 软件架构演变
- 中台层是后端将微服务集合,也就是将小的API聚合为大的中台层
![1554164802253](F:\我的学习\My Study\07-React\assets\1554164802253.png)
1.1.6.5 Middleware
![1554165066437](F:\我的学习\My Study\07-React\assets\1554165066437.png)
// logger.js logger中间件
export default const logger = ({getState, dispatch}) = next => action = {
console.log(action)
console.log('next state', getState)
const result = next(action)
return result
}
- 使用
// index.js 中
import logger from "./redux/logger";
const store = createStore(Reducer, applyMiddleware(thunk,logger))
1.1.6.6 Store enhancer
1. 一般结构
function enhancerCreator (){
return createStore => (...args) =>{
}
}
1.1.1 Fragment 占位符
- Fragment 占位符并不会被渲染成任何标签元素
import {Fragment} from 'react';
class App extends Component {
render (){
return (
<Fragment>
<div></div>
<div></div>
</Fragment>
)
}
}