React中如何使用Redux
导读:本文共7215.5字符,通常情况下阅读需要24分钟。同时您也可以点击右侧朗读,来听本文内容。按键盘←(左) →(右) 方向键可以翻页。
摘要: Redux 是一种状态容器 JS 库,提供可预测的状态管理,经常和 React 配合来管理应用的全局状态,进行响应式组件更新。Redux 一般来说并不是必须的,只有在项目比较复杂的时候,比如多个分散在不同地方的组件使用同一个状态。对于这种情况,如果通过 props 层层传递,代码会变得不可维护,这时候我们可以考虑使用 Redux 这类状态管理库。不使用 Redu... ...
目录
(为您整理了一些要点),点击可以直达。Redux 是一种状态容器 JS 库,提供可预测的状态管理,经常和 React 配合来管理应用的全局状态,进行响应式组件更新。
Redux 一般来说并不是必须的,只有在项目比较复杂的时候,比如多个分散在不同地方的组件使用同一个状态。对于这种情况,如果通过 props 层层传递,代码会变得不可维护,这时候我们可以考虑使用 Redux 这类状态管理库。
不使用 Redux 的写法
我们创建一个 User 组件,显示用户名,并支持设置用户名。先看看不使用 Redux 的写法。
import{Component,createRef}from'react';classUserextendsComponent{state={username:'前端西瓜哥'};inputRef=createRef();setUsername=()=>{this.setState({username:this.inputRef.current.value});};render(){return(<div><div>用户名:{this.state.username}</div><inputref={this.inputRef}type="text"/><buttononClick={this.setUsername}>设置用户名</button></div>);}}exportdefaultUser;
下面我们改造一下这个组件,将状态迁移到 Redux 里。
最底层的写法
Redux 是和框架无关的,我们先看看只用 Redux 库的写法。
demo:codesandbox.io/s/redux-pla…
首先我们创建一个 reducer。
//user_reducer.jsimport{SET_USERNAME}from'./constants';//初始值constdefaultState={name:'前端西瓜哥',age:88};//用于修改user状态的reducerexportconstuserReducer=(preState=defaultState,action)=>{switch(action.type){caseSET_USERNAME://type值都统一放到constantsreturn{...preState,name:action.payload};//这里还可以根据需要,添加类似setUserAga等逻辑default:returnpreState;}};
//constants.jsexportconstSET_USERNAME='SET_USERNAME';
reducer 是一个用于更新状态的函数,接收原来的状态 preState 和一个更新动作对象 action。
action 对象有一个 表示此次操作的描述 type
和 其他数据属性(通常为 payload)
。payload 会以某种方式去计算出一个新的状态,替换掉 redux 中原来的 state。
{type:'SET_USERNAME',payload:'新用户名'}
type 通常是一个字符串,比如我们会用 'COUNT_INCREMENT'
来给一个计数器加一,或用 'SET_USERNAME'
来更新用户名。reducer 会根据不同的 type 来执行不同的更新 state 行为。
action 的构造我们通常会用一个函数帮忙构建,这种函数称为 Action Creator:
//user_action.jsimport{SET_USERNAME}from'./constants';exportconstsetUsernameAction=(data)=>{return{type:SET_USERNAME,payload:data};};
有了 reducer,我们可以用它们来构建我们的 store。store 可以访问所有的保存在 redux 状态:
import{combineReducers,createStore}from'redux';import{userReducer}from'./user_reducer';conststore=createStore(combineReducers({user:userReducer}));exportdefaultstore;
combineReducers 可以将多个 reducer 组合在一起,有各自对应的属性名。比如上面的代码,我们可以通过 store.getState().user
来拿到用户对象。
如果你又新增了 counter 状态对象,只需再加上 counter: counterReducer
,就可以用 store.getState().counter
来拿到这个对象。
createStore 用于创建应用中所有的 state,然后这些 state 都会存放到这个被返回的 store 里。
现在我们的 User 组件就变成这样了:
import{Component,createRef}from'react';importstorefrom'../store/store';import{setUsernameAction}from'../store/user_action';classUserextendsComponent{inputRef=createRef();componentDidMount(){store.subscribe(()=>{this.setState({});});}setUsername=()=>{store.dispatch(setUsernameAction(this.inputRef.current.value));};render(){return(<div><div>用户名:{store.getState().user.name}</div><inputref={this.inputRef}type="text"/><buttononClick={this.setUsername}>设置用户名</button></div>);}}exportdefaultUser;
store.getState()
可以拿到 state 对象,通过它,我们获取到其下我们需要的对象,比如 user 对象。store.dispatch(action)
派发 action 对象,触发状态的更新。store.subscribe(fn)
订阅状态的变化,执行回调函数。这里我们一发现状态发生了变化,就立刻重新渲染组件。
Redux 本质是发布订阅模式,状态集中在一起,状态可以通过 store.getState()
访问,通过 store.dispatch(action)
改变状态,通过 store.subscribe(fn)
订阅状态变化(React 组件监听到变化后,重新渲染组件)。
这种写法是最原始的写法,可以用在任何框架中。
缺点很明显:用到 redux 的组件要订阅 state 变化,一变化就重新渲染组件。有时候其他组件的 state 变化了,当前组件也会进行不必要的重新渲染。
自己去判断吧,又太繁琐,容易写错,也容易忘记订阅。对于忘记订阅的问题,我们也可以直接把让根组件来监听和重新渲染,但这样性能很差。
接下来西瓜哥要讲的 React-Redux 库可以解决这个问题。它能够在当前组件用到的特定 state 发生改变时,才重新渲染组件。
React-Redux
发现大家都很喜欢在 React 里用 Redux,于是 Facebook 出了一个 React-Redux 库,让大家能够更好更正确地在 React 中使用 Redux。
React-Redux 配合 connect 高阶组件
我们先看看使用 connect 的写法。
demo:codesandbox.io/s/react-red…
React-Redux 引入了一个容器组件的概念,这个组件专门负责和 redux 打交道 。容器组件其实是一个高阶组件,将真正的 UI 组件做一个封装,在上面做了以下工作:
将 state 和 dispatch 映射到 props,注入到 UI 组件中
监听 state 变化,必要时重新渲染 UI 组件。
高阶组件:一个函数,它会接收组件参数,然后返回一个新的组件。高阶组件的作用是对真正的 UI 组件做一些复用的逻辑的封装,通常用于做功能增强。
随着 React Hooks 愈发流行,大家现在更喜欢用 React Hooks 来取代高阶函数,写法更优雅。
constContainerComponent=connect(mapStateToProps,mapDispatchToProps,)(UIComponent);
现在开始改造项目。
我们创建一个 container 文件夹,里面放上 User.jsx 文件,里面写上如下内容:
//containers/User.jsximport{connect}from'react-redux';importUserUIfrom'../components/User';import{setUsernameAction}from'../store/user_action';exportdefaultconnect(//mapStateToProps(state)=>({user:state.user}),//mapDispatchToProps(dispatch)=>({setUsername:(newName)=>dispatch(setUsernameAction(newName))}))(UserUI);
然后记得在使用该容器的地方,传入我们的 store 对象,如下:
importUserContainerfrom'./containers/User';importstorefrom'./store/store';import'./styles.css';exportdefaultfunctionApp(){return<UserContainerstore={store}/>;}
当然每个容器组件都要传入 store 未免太麻烦,我们通常会使用另一种做法:使用 redux-react 提供的 Context Provider,包裹住根组件,如下:
import{Provider}from'react-redux';ReactDOM.render(<Providerstore={store}><App/></Provider>,document.getElementById('root'));
然后是 UI 组件的改造:
import{Component,createRef}from'react';classUserextendsComponent{inputRef=createRef();render(){return(<div><div>用户名:{this.props.user.name}</div><inputref={this.inputRef}type="text"/><buttononClick={()=>this.props.setUsername(this.inputRef.current.value)}>设置用户名</button></div>);}}exportdefaultUser;
UI 组件的 props 会拿到 user 对象、setUsername 方法以及我们注入的 store 对象(如果用 Context 的方式则取不到)。
使用了 connect 后,只有组件用到的 state 改变了,才会触发组件的更新。
这里有个需要特别注意的地方,就是你要 保证新的状态对象和旧状态不相等,这样才能触发组件重新渲染,这在处理对象方法时容易出错。你需要拷贝一个新的对象作为新的状态,推荐使用扩展运算符的写法。
//user_reducer.js//错误的写法,新的state依旧指向原来的对象preState.name=action.payload;returnpreState;//正确的写法return{...preState,name:action.payload};
或者可以考虑使用 immer 这种不可变数据结构库。
React-Rudex 配合 React Hooks
前面我们用了 connect 这么一个高阶组件,是为了给 UI 组件增强功能。
说到增强功能,react-redux 也提供了现在非常流行的 React Hooks 写法,写起来更优雅,也是目前西瓜哥我所在公司的做法。
这里我们就不需要 connect 高阶组件了,也就是说不需要容器组件。
demo:codesandbox.io/s/react-red…
//User.jsimport{useEffect,useRef}from'react';import{useDispatch,useSelector}from'react-redux';import{setUsernameAction}from'../store/user_action';constUser=()=>{//获取状态constuser=useSelector((state)=>state.user);//获取dipatch方法constdipatch=useDispatch();constinputRef=useRef(null);return(<div><div>用户名:{user.name}</div><inputref={inputRef}type="text"/><buttononClick={()=>{constnewName=inputRef.current.value;dipatch(setUsernameAction(newName));}}>设置用户名</button></div>);};exportdefaultUser;
通过 useSelector 我们可以拿到通过上下文绑定的 state,然后从中获取我们需要用到的状态。
constuser=useSelector((state)=>state.user);
如果有多个,我们可以写成对象的形式:
const{user,counter}=useSelector((state)=>({user:state.user,counter:state.counter}));
是不是有点像 connect 的 mapStateToProps。
然后是获取 dispatch 方法:
constdipatch=useDispatch();
hook 非常优雅,但我也发现,相比 connect 写法,我们的 redux 状态逻辑和组件耦合在一起了。不过一般我们的组件都是业务组件,还是可以接受的。
Redux Toolkit
我们可以看到,我们要维护一个状态,我们要写 reducer 方法、action creator 方法,还要用一个 contants.js 文件集中式管理所有的 actionType 字符串。
你发现你写了非常多的 模板代码,每加一个 state 就要创建上面这些东西,各个文件里跑来跑去,人都麻了。
于是 redux 又出了一个工具集库 Redux Toolkit,来解决这个问题。
demo:codesandbox.io/s/redux-too…
Redux Toolkit 提供了 createSlice 方法,可以帮你用更少的代码生成配套的 reducer 和 action,而且有很好的可维护性。
//userSlice.jsimport{createSlice}from'@reduxjs/toolkit';constuserSlice=createSlice({name:'user',initialState:{name:'前端西瓜哥',age:88},reducers:{setUsername:(state,action)=>{//因为ReduxToolkit内置使用了immer,所以可以直接改。state.name=action.payload;}}});//actionsexportconst{setUsername}=userSlice.actions;//获取自己需要的state,用在组件的userSeletorhook上。exportconstselectUser=(state)=>state.user;//reducerexportdefaultuserSlice.reducer;
createSlice 传入 name(标识符,生成 actions 要用到)、initialState(初始值)、reducers(变成了对象形式)参数,然后返回一个对象。
这个返回的 slice 对象有 actions 对象属性,比如上面的代码,actions 下有一个 setUsername 的方法,执行后会返回 {type: "user/setUsername", payload: "新名字"}
。
可以看到 action 的 type 是根据 name 和 reducers 的属性生产的,确保唯一性。
slice 还有一个 reducer 对象,其实就是将前面传入的 reducers 配合自动生成的 action 转换为了函数的形式。
createSlice 干了什么事?createSlice 将原来管理一个状态但代码却是分离的 action 和 reducer 集中在了一起,不用自己去起 actionType 的名字。
然后是生成 store 也要改成 configureStore 的写法:
//store.jsimport{configureStore}from'@reduxjs/toolkit';importuserReducerfrom'./userSlice';conststore=configureStore({reducer:{user:userReducer}});exportdefaultstore;
</div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
React中如何使用Redux的详细内容,希望对您有所帮助,信息来源于网络。