Redux
基础概念
- store:仓库
- reducer:处理器
- state:状态
- dispatch:派发「函数」
- action:动作「必须是一个对象」
- scbscribe:订阅
Redux 的作用
为了解决 state 数据的管理问题
Redux 解决的问题
- react 中,数据在组件中是单向数据流
- 若组件之间是父子组件,数据从父组件到子组件(通过 props)
- 若组件之间非父子组件(或者是兄弟组件)之间的通信就比较麻烦,由此,Redux 来解决问题了
Redux 设计思想
- 将整个状态存储到一个仓库中,就是 store
- 里面存储的是状态树,state tree
- 组件可以派发 dispatch 行为 action 给 store,并不是直接通知的其他组件
- 其他组件可通过订阅 store 中的 state,来刷新自己的视图
Redux 的三大原则
三大原则
- 单一数据源
- 所有的对象只存在于唯一一个 store 中
- 所有的数据都存在一个对象中
- State 是只读的
- 改变 state 中数据的方法就是触发 action「action 就是一个 JS 对象」
- 使用纯函数来执行修改
- 纯函数:不会对本作用域以外造成影响
- Reducer 只是一个纯函数,它接收先前的 state 和 action,并返回新的 state
- dispacth 执行实则是 让 reducer 执行
action
- 改变 state 中数据的方法就是触发 action「action 就是一个 JS 对象」
- 使用 action 来描述所有变化带来的好处就是可以清晰地知道应用中到底发生了什么
createStore
- 通过 createStore 产生一个 store,这里通过 createStore 传递一个 reducer 函数
- reducer 就是一个根据 action({type: 'xxx', 自定义属性名: 'xxx'}) 来更新 state 的纯函数
- 参数
- reducer:处理器,函数类型
- 参数 1:旧的状态
- 参数 2:action,派发的动作
- 初始状态:对象
- reducer:处理器,函数类型
store 中的属性和方法
subscribe
- 通过 subscribe(listener) 注册监听器
- 数据更新完成之后,回调函数执行
- 借助事件池:listeners = [];
- 每次 subscribe 执行,都会在事件池中放入一个事件
- subscribe 返回值执行,会把当前事件移除「过滤器移除:listeners.filter(item=>item!==f)」
- 通过 subscribe(listener) 返回的函数注销监听器
- 在 componentDidMount 生命周期中调用 store 中的 subscribe 方法执行 setState 来更新视图
- subscribe 参数是一个函数,返回值是一个函数
- 返回值执行会把当前这个事件移除掉「在 componentWillmount 钩子中执行」
getState:获取 state
- 提供 getState() 方法获取 state
- 页面加载时触发 getState,可能会报错,此时 state 为 undefined,所以,需要主动触发一次 dispatch({})
- dispatch 执行,reducer 执行,reducer 函数执行,state 会避免 undefined 值
dispatch:提供 dispatch(action) 方法更新 state
- 数据更新操作
- 数据更新完成之后,调用事件池中的方法
listeners.forEach(item=>{
item&&item();
})
actionCreators
- 可以派发一个 actionCreators 函数
- 也就是把 dispatch 中传给 action 的对象,封装成一个函数,在返回这个对象
- 减少在 dispatch 中拼接对象
// 创建的 actionCreator
const actions = {add, minus}
bindActionCreators
- 省去 store.dispatch 的写法
- 参数 1:actions
- 参数 2:dispatch
const actions = {add, minus};// add, minus 是封装好后的函数名
const boundActions = bindActionsCreators(actions, store.dispatch);
combineReducers:合并 reducer
- redux 中只有一个 reducer,只有一个仓库,reducer 只有一个状态,会难以维护
- 每个组件对应一个 reducer
- 每个组件也有对应的 action 动作
- 后期在创建仓库时,无需传递默认值
- 多个 reducer 的话,需要通过 combineReducers 进行 reducer 的合并
- combineReducers 是通过 for 循环执行了所有reducers
- 当我们执行 dispatch 的时候,他会把所有的 reducer 都执行一遍
- 如果多个 reducer 中的方法重复了,那么所有方法都会执行
- 多个 reducer 一般项目中需要拆分成一个单独的文件
- 重复之后会造成不可预想的错误,所以,要单独创建一个常量 JS 文件
- 避免每个组件都要引入常量 JS 文件我们可以把 dispatch 的对象单独创建一个 JS 文件,后期易维护:export const CHANGE = "CHANGE";
let rootReducer = combineReducers({
qqq: CountReducer,
aaa: ColorReducer
})
let store = createStore(rootReducer);
React-redux
Provide:供应商
- 接收 store 属性,并向下传递
- 使用 Provider 组件把根组件包裹起来,然后给 Provider 传一个 store,对应的值就createStore 产生的那个 store
- 根组件包裹起来的目的就是为了让其根组件及后代组件都可以使用 store 中的数据
connect:高阶组件
- 负责把仓库和组件进行关联,通过 context 获取 store
- 不需要手动引入仓库
- 不需要手动绑定 action
- connect 「就是一个高阶组件」处理
- 新组件 = connect(回调函数1,回调函数 2)(要处理的组件名)
- 渲染时,渲染的都是新组件
- 回调函数 1 和 回调函数 2,返回值必须是一个对象,这个对象中的属性,最终会被传递给要处理的组件
- 回调函数 1 接收一个 state 参数
- 这是一个映射函数,可以把仓库的状态进行映射出来分状态,分状态会成为组件属性对象
// counter:组件名 mapStateProps = state=>state.counter - 回调函数 2 接收一个 dispatch 参数,可以忽略
- 如果只有一个回调函数,那么可以使用 this.props.dispatch 来更新数据
- 处理完成的组件可以通过 props 获取,两个回调函数的返回结果
- 新组件 = connect(回调函数1,回调函数 2)(要处理的组件名)
- 哪个后代组件想要使用 redux 中的数据,就需要使用 connect 处理
redux-thunk
解决接收函数
- action 必须是一个对象,如果是函数的话,报错
- 所以我们需要中间件:redux-thunk,来解决接收函数报错的信息
解决同步流程
- action -> reducer
- 派发(dispatch)行为(action)后,直接去 reducer 执行相应的动作,这相当于同步操作
- 如果我们想实现异步操作,那么就需要引入中间件改变 redux 同步执行的流程
- action -> middewares -> reducer
- 比如:点击按钮相当于 dispatch 触发 action,接下来就是中间件,去获取服务器数据 middleware 的执行,当中间件成功获取到服务器就去触发 reducer 对应的动作,更新渲染视图的数据
- 中间件的机制可以让我们改变数据流,实现比如:异步 action、action 过滤、日志输出、异常报告等功能
Hooks
useDispatch
- 在函数组件中,获取到 store.dispatch 方法
// 引入
import {useDispatch} from 'react-redux'
let dispatch = useDispatch();// store.dispatch
let state = useSelector(state=>state.counter)// 获取状态
// 使用
dispatch({type: 'xxx'})
useSelector
- 使用选择器
connected-react-router
- 是一个把 redux 和路由连接在一起的库
- 可以通过派发动作方式跳转路径
- 可以在仓库里获取最新路径信息:store 可以存放 location action
使用方法
- 创建一个 history 文件
import {createBrowserHistory} from 'history'
let history = createBrowserHistory();
export default history;
- 创建 store 仓库
- 引入:ConnectedRouter
- ConnectedRouter 需要上下文中的 store,所以需要使用 react-redux 中的 Provider
- 属性:history={history}
- 包裹根组件,并传参:
<Provider store={store}>
<ConnectedRouter history={history}> ...
</ConnectedRouter>
</Provider>