使用场景
Redux 的适用场景:多交互、多数据源。
从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。
- 某个组件的状态,需要共享
- 某个状态需要在任何地方都可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
工作流程
首先,用户发出 Action。
store.dispatch(action);
然后,Store 自动调用 Reducer,并且传入两个参数:当前 State 和收到的 Action。 Reducer 会返回新的 State 。
let nextState = todoApp(previousState, action);
State 一旦有变化,Store 就会调用监听函数。
// 设置监听函数
store.subscribe(listener);
listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
function listerner() {
let newState = store.getState();
component.setState(newState);
}
API介绍
action:
View的展示是根据State数据而来的,想要View变化,只能改变State(存在store里),想要修改State就必须发出一个action通知,store接收到通知后处理state,从而改变View。
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,
const action = {
type: "ADD",
num: 1,
}
reducer:
Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。
Reducer 函数最重要的特征是,它是一个纯函数。也就是说,只要是同样的输入,必定得到同样的输出。
const reducer = (state = 10, action) => {
switch (action.type) {
case "ADD":
return state + action.num
case "SQUARE":
return state * state
default:
return state
}
}
store:
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何UI组件都可以直接从store访问特定对象的状态,其具有dispatch,subscribe,getState方法(敲黑板划重点)。
import { createStore } from 'redux';
const store = createStore(reducer);
createStore方法还可以接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。
import { createStore } from 'redux';
const initalState = {
name:'ade'
}
const store = createStore(reducer,initalState);
store.subscribe()
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
store.dispatch()
store.dispatch()是 View 发出 Action 的唯一方法,这就需要在View中引入store然后调用dispatch派发Action,dispatch一调用就会调用reducer来改变state从而改变View。
store.dispatch(action)
store.getState()
getState方法可以获取返回当前state的值,可以在任意位置打印state的值。
console.log(store.getState())
State
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过store.getState()拿到。
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
示例
import React from "react"
import { createStore } from "redux"
const addOne = {
type: "ADD",
num: 1,
}
const addTwo = {
type: "ADD",
num: 2,
}
const square = {
type: "SQUARE",
}
const reducer = (state = 10, action) => {
switch (action.type) {
case "ADD":
return state + action.num
case "SQUARE":
return state * state
default:
return state
}
}
const store = createStore(reducer)
// 获取state
console.log(store.getState())
// 操作
console.log(store.dispatch(addOne))
console.log(store.getState())
console.log(store.dispatch(addTwo))
console.log(store.getState())
console.log(store.dispatch(square))
console.log(store.getState())
function App() {
return (
<div className='App'>
<h1>123</h1>
</div>
)
}
export default App
中间件与异步操作
applyMiddleware
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(logger)
);
createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);
store.dispatch方法正常情况下,参数只能是对象,不能是函数。
所以使用redux-thunk中间件,改造store.dispatch,使得后者可以接受函数作为参数。
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
);
异步action
const getAction = () => {
return (dispatch, getState) => {
fetch("https://api.github.com/users/ruanyf")
.then(res => res.json())
.then(data => {
console.log(data)
dispatch({
type: "GET",
num: data.id,
})
})
}
}
React-Readux的用法
connect()
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
import { connect } from 'react-redux'
export default connect(mapStateToProps, mapDispatchToProps)(Container)
mapStateToProps()
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。
const mapStateToProps = state => {
return {
num: state,
}
}
它接受state作为参数,返回一个对象。
mapStateToProps会订阅 Store,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染。
mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。
const mapStateToProps = (state, ownProps) => {
return {
isActive: ownProps.filter === state.visibilityFilter
}
}
mapDispatchToProps
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。
也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。
同样它接受2个参数 ,第一个是dispatch,第二个是ownProps
const mapDispatchToProps = dispatch => {
return {
add: value => dispatch(addAction(value)),
square: () => dispatch(squareAction()),
get: () => dispatch(getAction()),
}
}
mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。
组件
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。
React-Redux 提供Provider组件,可以让容器组件拿到state。
import React, { useState } from "react"
import Container from "./components/container"
import { Provider } from "react-redux"
import store from "./store/store"
function App() {
return (
<div className='App'>
<Provider store={store}>
<Container />
</Provider>
</div>
)
}
export default App
示例
APP.js 使用Provider 注入store到app.js供全局使用
import React, { useState } from "react"
import Container from "./components/container"
import { Provider } from "react-redux"
import store from "./store/store"
function App() {
return (
<div className='App'>
<Provider store={store}>
<Container />
</Provider>
</div>
)
}
export default App
reducer.js 定义这个组件的 Reducer。
const math = (state = 10, action) => {
switch (action.type) {
case "ADD":
return state + action.num
case "SQUARE":
return state * 2
case "GET":
return action.num
default:
return state
}
}
export default math
生成store对象,并使用Provider在根组件外面包一层。
import math from "../reducer/math"
import { createStore, applyMiddleware } from "redux"
import thunk from "redux-thunk"
const store = createStore(math, applyMiddleware(thunk))
export default store
import React, { useState } from "react"
import { addAction, squareAction, getAction } from "../actions/actions"
import { connect } from "react-redux"
const Container = props => {
console.log(props)
const { num, add, square, get } = props
return (
<div>
<button onClick={() => {add(1)}}>+1</button>
<button onClick={() => {add(2)}}>+1</button>
<button onClick={() => {square()}}>+1</button>
<button onClick={() => {get()}}>+1</button>
<h1>{num}</h1>
</div>
)
}
const mapStateToProps = state => {
return {
num: state,
}
}
const mapDispatchToProps = dispatch => {
return {
add: value => dispatch(addAction(value)),
square: () => dispatch(squareAction()),
get: () => dispatch(getAction()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Container)
action.js
import { ADD, SQUARE } from "../types/types"
const addAction = num => {
return {
type: ADD,
num,
}
}
const squareAction = () => {
return {
type: SQUARE,
}
}
const getAction = () => {
return (dispatch, getState) => {
fetch("https://api.github.com/users/ruanyf")
.then(res => res.json())
.then(data => {
console.log(data)
dispatch({
type: "GET",
num: data.id,
})
})
}
}
export { addAction, squareAction, getAction }
上面的代码是不是看着很复杂,没错很复杂,那我们用一个新的插件Redux Toolkit。
Redux Toolkit
没错上面 reducer action state的代码可以直接简化为一个文件。
import { createSlice } from "@reduxjs/toolkit"
const initialSate = {
num: 0,
}
const mathCount = createSlice({
name: "math",
initialState: initialSate,
reducers: {
addNum: (state, action) => {
state.num += action.payload
},
},
})
export const { addNum } = mathCount.actions
export default mathCount.reducer
生成store
import { configureStore } from "@reduxjs/toolkit"
import mathCount from "../features/mathCount"
export default configureStore({
reducer: {
math: mathCount,
},
})
在app.js注入
import React, { useState } from "react"
import Container from "./components/container"
import { Provider } from "react-redux"
import store from "./store/store"
function App() {
return (
<div className='App'>
<Provider store={store}>
<Container />
</Provider>
</div>
)
}
export default App
组件使用
import React, { useState } from "react"
import { useSelector, useDispatch } from "react-redux"
import { addNum } from "../features/mathCount"
const Container = props => {
const dispatch = useDispatch()
const { num } = useSelector(state => {
return state.math
})
return (
<div>
<button
onClick={() => {
dispatch(addNum(1))
}}
>
+1
</button>
<button
onClick={() => {
dispatch(addNum(10))
}}
>
+10
</button>
<h1>{num}</h1>
</div>
)
}
export default Container