携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情
由Vue转React,耕耘下redux笔记
建议写个todoList来练手,以此熟悉该技术: 刚写的todolist ,可clone玩玩。
前言:React-Redux, Redux, React三者之间的关系
一张图说清 React-Redux ,Redux , React 三者到底是什么关系。
- Redux: 首先 Redux 是一个应用状态管理js库,它本身和 React 是没有关系的,换句话说,Redux 可以应用于其他框架构建的前端应用,甚至也可以应用于 Vue 中。
- React-Redux:React-Redux 是连接 React 应用和 Redux 状态管理的桥梁。React-redux 主要专注两件事,一是如何向 React 应用中注入 redux 中的 Store ,二是如何根据 Store 的改变,把消息派发给应用中需要状态的每一个组件。
- React :就不说了
redux篇
以todolist为例,redux在使用中大致表现为UI组件、action、constants、reduces四大部分:
整体大致流程为:首先在 constants 设置 type 参数,接着action声明要修改的对象类型(补上type参数),然后在 reduce(接收旧的 state 和 action 两个参数,返回新的 state ) 写修改数据的逻辑,最后在UI组件触发逻辑。
下面开始分别介绍:
Contants:
设置Action需要的type参数字段,Reduce会依据这字段做不同逻辑
比如删除逻辑的字段:
export const TODO_DEL = "TODO_DEL";
Action:只描述state的变化而不更新
在 Redux 中,action 本质是一个JavaScript 普通对象,可以理解为 store 数据的载体。
官网说:唯一改变 state 的方法就是触发 action,那是因为在UI组件中执行了dispatch(todoDel (!nowDone));
操作, dispatch 了 action 暴露出来的 todoDel ,然后这个 todoDel 执行了 reduce 逻辑,然后才发生了 state 改变。有点绕,现在看不懂没关系,先在这打个标记,往下看,一会往回翻就明白了。
//action文件
export const todoDel = (id) => ({
type: TODO_DEL,
id,
});
在UI组件里,只需把 action 创建的结果 ↑ 传给 dispatch()
方法,即可发起一次修改或更新数据(前提是reduce里写了逻辑)。
dispatch(todoDel (text))
//或者创建一个被绑定的 action 函数来自动 dispatch:
const boundAddTodo = text => dispatch(addTodo(text))
//然后直接调用它们:
boundAddTodo(text);
store 里能直接通过 store.dispatch()
调用 dispatch()
方法,但多数情况下会使用 react-redux 提供的 connect()
来调用。bindActionCreators()
可以自动把多个 action 创建函数绑定到 dispatch()
方法上。
Reducer:根据Action变化更新State
Reducers :就是纯函数,它接收旧的 state 和 action两个参数,返回新的 state的函数。且依据应用状态的变化响应 actions 并将数据更新。
注意每个 reducer 只负责管理全局 state 中它负责的一部分。每个 reducer 的 state
参数都不同,分别对应它管理的那部分 state 数据。
//TODO_DEL:依据type参数,处理不同的action逻辑
export default function todo(state = 旧state数据, action) {
switch (action.type) {
case TODO_DEL:
return state.filter((item) => item.id !== action.id);
case TODO_CHEK_ALL:
return state.map((item) => ({ ...item, done: action.done }));
...
default:
return state;
}
}
永远不要在 reducer 里做这些操作:(详情见官网)
- 修改传入参数;
- 执行有副作用的操作,如 API 请求和路由跳转;
- 调用非纯函数,如
Date.now()
或Math.random()
。 - 使用
combineReducers()
将多个 reducer 合并成为一个
谨记 reducer 一定要保持纯净。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
UI组件
上面一套流程走完后,就可以在UI组件触发事件,更新数据了。
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
const handleChangeAll = () => {
dispatch(todoCheck(!nowDone));
};
Store:将它们联系到一起的对象
创建一个 Redux store 来以存放应用中所有的 state:
createStore(reducer, [preloadedState], enhancer)
- reducer:接收旧的 state 和 action,返回新的 state树
Store 有以下职责:
- 维持应用的 state;
- 提供
getState()
方法获取 state; - 提供
dispatch(action)
方法更新 state; - 通过
subscribe(listener)
注册监听器; - 通过
subscribe(listener)
返回的函数注销监听器。
发起 Actions
// 打印初始状态
console.log(store.getState())
// 注意 subscribe() 返回一个函数用来注销监听器
const unsubscribe = store.subscribe(() =>
console.log(store.getState())
)
// 发起一系列 action
store.dispatch(reducer的方法('Learn about actions'))
// 停止监听 state 更新
unsubscribe();
数据流
Redux的数据流在上面在开始的地方已经都是说过了,这里无非就是在重诉一遍,啰嗦一下。Redux 应用中数据的生命周期遵循下面 4 个步骤:
- 调用Action,可以在任何地方
store.dispatch(action)
。
//Action 就是一个描述“发生了什么”的普通对象
{ type: 'LIKE_ARTICLE', articleId: 42 }
- store将(当前的 state 树和 action) 传入 reducer 函数():
// 当前应用的 state
let previousState = {
A: [],
B: "hello"
}
// 将要执行的 action
let action = {
type: 'ADD_TODO',
text: 'Understand the flow.'
}
let nextState = todoApp(previousState, action)
- 根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
Redux 原生提供combineReducers()
辅助函数,来把根 reducer 拆分成多个函数,用于分别处理 state 树的一个分支。
//假如有两个 reducer
function A(state = [], action) {
return nextState
}
function B(state = 'Redux', action) {
return nextState
}
let todoApp = combineReducers({
A,
B
})
当你触发 action 后,combineReducers
返回的 todoApp
会负责调用两个 reducer:
let nextA = A(state.A, action)
let nextB = B(state.B, action)
然后会把两个结果集合并成一个 state 树:
return {
A: nextA,
B: nextB
}
- Redux store 保存了根 reducer 返回的完整 state 树
这个新的树就是应用的下一个 state!所有订阅 store.subscribe(listener)
的监听器都将被调用;监听器里可以调用 store.getState()
获得当前 state。
React Redux 应该调用 component.setState(newState)
来更新。
一些Api
说完redux,那就离不开这里面的一些Api和一些react的一些Hooks了,都很重要。
useSelector
selector 回调函数会把storeState返回给你 你再进行筛选返回自己想要使用的数据
const num = useSelector(state => state.num);
createStore
通过createStore将state存入store
const store = createStore(reducer, initialState);
再通过Provider向子组件暴露store,通过store在父子组件之间共享状态
<Provider store={store}>
<Son />
</Provider>
useDispatch
通过useDispatch
可以获取dispatch,用来提交更新
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
dispatch(todoDel(id));
context :createContext -> useContext
一种组件传值方式,能轻松拿到组件值。官方Hooks的API
//null可以为父组件要传递的值,不一定是null
const newContext = createContext(null);
const [age,setAge] = setAge(0)
//通过value传递值
<newContext.Provider value={{age,setAge}}>
<Father>
<Son />
</Father>
</newContext.Provider >
//son组件
const {age,setAge} = useContext(newContext)
const add=()=>{
setAge(age=>age+1)
};
useEffect(callback,[])
React会在每次渲染完后调用useEffect,包括第一次加载渲染DOM
接受两个参数,一个处理函数,另一个关联的状态或数组,这个变了就重新执行
useCallback + useMemo
他两个使用和 useEffect 差不多
解决的痛点:在函数组件中,定义在组件内的函数会随着状态更新而重新渲染,这样会影响的子组件频繁定义、渲染。
//父组件
function Father(){
const [age,setAge]=useState(0)
const handleClick = () => {
setAge(age + 1)
}
return <Son />
}
采用useCallback +Memo后:
//使用Memo后确实不会影响了,但父组件传值过来呢
const Son = React.memo(function Son(){
...
})
//父组件:这样就不会
const childClick = useCallback(() => {}.[])
<Son click={childClick} />
以后在更吧,现在写倦了...