React react-redux的使用
由于redux是一个独立的库,我们如果需要想在react中更方便的使用redux,推荐使用react-redux
这个库,当然不使用这个库也是可以的,那么你需要自己维护全局state
,并且订阅state的更新,去修改视图,而react-redux已经帮我们做好了这些东西,并且提供了一些api可以使用。
安装
首先我们需要安装redux
和react-redux
yarn add redux react-redux
定义store
然后我们需要定义store
,我们在这里创建一个姓名和年龄的store
,并combineReducers
,一般存放在单独的文件中,我们放在store.js
中
import { createStore, combineReducers } from 'redux'
const ageReducer = (state = {age: 18}, action) => {
switch (action.type) {
case 'increment':
return {...state, age: state.age + 1}
case 'decrement':
return {...state, age: state.age - 1}
default:
return state
}
}
const nameReducer = (state = {name: 'jerry'}, action) => {
switch (action.type) {
case 'changeName':
return {...state, name: action.payload.name}
default:
return state
}
}
const reducers = combineReducers({ageReducer, nameReducer});
const store = createStore(reducers)
export default store
在react中使用
如何将store
和react app
之间关联起来呢?这就是react-redux
的作用,react-redux
提供一个Provider
组件,将store
绑定到组件,一般我们直接绑定到根组件app
上,那么子组件中也可以使用store
了
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
在组件中使用
store
已经可用,但是在组件中要怎么使用,怎么拿到state
,又怎么派发action
呢?此时需要使用到react-redux
提供的connect
高阶组件了,这个是可选的,如果你的组件不需要访问store
,就可以不使用connect
。
来看看具体用法,假设我们有一个组件User.js
,渲染姓名和年龄,注意我们使用的时候不提供name
和age`` props
import React from 'react'
class User extends React.Component {
constructor(props){
super(props)
}
render() {
return <div>
姓名: {this.props.name}
<br />
年龄: {this.props.age}
</div>
}
}
export default User
此时肯定是渲染不出来的,因为我们还没有使用connect
连接store
,<User />
上也没有提供相应的props
。
现在我们来连接store
,就使用到了connect
高阶组件,connect
接收两个函数作为参数,我们习惯性的称之为mapStateToProps
和mapDispatchToProps
,顾名思义,就是把state
和dispatc
h映射到props
上,使我们的组件可以通过props
属性访问state
和dispatch
,通过dispatch
,就可以派发action
了。第二个函数是可选的,如果不传,会直接把dispatch
映射到组件的props
上
mapStateToProps
我们先来看看mapStateToProps
怎么用,我们打印一下state
,看看是什么东西
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
render() {
return <div>
姓名: {this.props.name}
<br />
年龄: {this.props.age}
</div>
}
}
const mapStateToProps = (state) => {
console.log(state)
}
const ConnectedUser = connect(mapStateToProps)(User)
export default ConnectedUser
可以看到,就是state
对象,注意我们是使用combineReducers
过的,是有key
的,具体可以查看Redux combineReducers
从上图报错也可以看出来,mapStateToProps
需要返回一个纯对象,因此我们可以拿到所有的state
后,只挑选出对自己有用的,如果都需要或者图方便,直接返回{...state}
即可
// ...
const mapStateToProps = (state) => {
return {...state}
}
// ...
此时我们就可以在组件的props
属性上获取到state
了,渲染的时候要根据state
的结构进行渲染,因此上面的this.props.name
和this.props.age
需要修改一下
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
render() {
console.log(this.props)
return <div>
姓名: {this.props.nameReducer.name}
<br />
年龄: {this.props.ageReducer.age}
</div>
}
}
const mapStateToProps = (state) => {
console.log(state)
return {...state}
}
const ConnectedUser = connect(mapStateToProps)(User)
export default ConnectedUser
实际上,如果我们的组件有自己的props
,例如有一个性别属性<User gender="male" />
,mapStateToProp
函数还接受第二个参数,可以获取到组件自己的属性,如果只传了state
参数,会自动进行合并,不幸的是,如果组件自身的props和state的属性有重名的,组件自身的属性将会丢失,因此对于这种情况,mapStateToProp
函数让你接受第二个参数,就是组件自身的props
,具体保留哪个,我们可以自行指定。
例如我们是组件以自身的属性优先,就可以直接return { ...state, ...props }
,此时组件自身的值总是会覆盖state
的值
例如这样使用组件<User ageReducer="male" />
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
render() {
console.log(this.props)
return <div>
姓名: {this.props.nameReducer.name}
<br />
年龄: {this.props.ageReducer.age}
</div>
}
}
const mapStateToProps = (state, props) => {
console.log(state)
return {...state, ...props}
}
const ConnectedUser = connect(mapStateToProps)(User)
export default ConnectedUser
看完记得记得把
<User ageReducer="male" />
还原
仅在需要时返回新的对象引用
我们从store
中取出的state
还可以结合组件自身props
进行一些计算操作,例如排序,复杂计算等,但是如果非常耗时,可能会影响性能.
react-redux
内部实现了shouldComponentUpdate
方法以便在组件用到的数据发生变化后能够精确地重新渲染。默认地,react-redux
使用===
对mapStateToProps
返回的对象的每一个字段逐一对比,以判断内容是否发生了改变。
react-redux
进行浅比较来检查mapStateToProps
的结果是否改变了。返回一个新对象或数组引用十分容易操作,但会造成你的组件在数据没变的情况下也重新渲染。
我们建议将所有复杂的查找和计算数据的方法封装到selector
中。此外,你今后可以通过使用Reselect编写“memoized
” selectors
来跳过不必要的工作从而优化性能。还可以将复杂计算放到组件内部去做计算,使用useMeme
或useCallback
进行优化
mapDispatchToProps
在来看看如何dispatch
一个action
,上面看到,dispatch
函数已经是默认被传递到组件的props
属性上了,如果不做操作,可以直接使用this.props.dispatch
派发一个action
例如:
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
handleIncrementClick = () => {
this.props.dispatch({type: 'increment'})
}
handleDecrementClick = () => {
this.props.dispatch({type: 'decrement'})
}
render() {
console.log(this.props)
return <div>
姓名: {this.props.nameReducer.name}
<br />
年龄: {this.props.ageReducer.age}
<br />
<button onClick={this.handleIncrementClick}>+</button>
<button onClick={this.handleDecrementClick}>-</button>
</div>
}
}
const mapStateToProps = (state, props) => {
console.log(state)
return {...state, ...props}
}
const ConnectedUser = connect(mapStateToProps)(User)
export default ConnectedUser
这样做没问题,但是直接在组件内部派发action
,组件里面可能很多地方都需要dispatch
,分散在组件的各个地方,代码维护起来就会十分困难,如果在组件内部还要继续向子组件里面传递dispatch
,再在子组件内部派发action
,就非常混乱了,并且暴露了父层的dispatch
给子组件了,子组件如果内部还有自己的connect
,就会非常混乱。
此时借助connect
的第二个参数,我们可以在mapDispatchToProps
里面集中定义组件的需要的dispatch
方法,再传给组件,这样集中管理起来代码维护起来成本就降低很多了。
怎么用呢?mapDispatchToProps
接收dispatch
作为参数,返回一个派发action
的函数对象,一旦定义了这个函数,dispatch
就不会默认传给组件了,需要自己定义。
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
handleIncrementClick = () => {
this.props.ageIncrement()
}
handleDecrementClick = () => {
this.props.ageDecrement()
}
handleNameChange = () => {
this.props.changeName({name: 'tom'})
}
render() {
console.log(this.props)
return <div>
姓名: {this.props.nameReducer.name}
<br />
年龄: {this.props.ageReducer.age}
<br />
<button onClick={this.handleIncrementClick}>+</button>
<button onClick={this.handleDecrementClick}>-</button>
<button onClick={this.handleNameChange}>改变名字</button>
</div>
}
}
const mapStateToProps = (state, props) => {
console.log(state)
return {...state, ...props}
}
const mapDispatchToProps = (dispatch) => {
return {
ageIncrement: payload => dispatch({type: 'increment', payload}),
ageDecrement: payload => dispatch({type: 'decrement', payload}),
changeName: payload => dispatch({type: 'changeName', payload}),
}
}
const ConnectedUser = connect(mapStateToProps, mapDispatchToProps)(User)
export default ConnectedUser
这样的话,我们自己定义的派发action
函数ageIncrement
、ageDecrement
、changeName
就会放到组件的props
属性上了
mapDispatchToProps简写形式
使用上述方式定义mapDispatchToProps
看起来有点累赘,react-redux
也帮我们想到了,在connect
函数的第二个参数上,可以直接传递一个action creator
函数的数组,然后使用bindActionCreators
API来帮我们进行处理,bindActionCreators
的使用具体请看Redux bindActionCreators
我们对mapDispatchToProps
进行改写,这样看起来好多了,功能也没有受到影响。
// ...
// const mapDispatchToProps = (dispatch) => {
// return {
// ageIncrement: payload => dispatch({type: 'increment', payload}),
// ageDecrement: payload => dispatch({type: 'decrement', payload}),
// changeName: payload => dispatch({type: 'changeName', payload}),
// }
// }
const ConnectedUser = connect(mapStateToProps, {
ageIncrement(payload) {
return {type: 'increment', payload}
},
ageDecrement(payload) {
return {type: 'decrement', payload}
},
changeName(payload) {
return {type: 'changeName', payload}
}
})(User)
export default ConnectedUser
react-redux
的基本使用就这些了,如果想要继续深入学习,可以看一下它的源码,和中文文档
现在都流行hooks
,react-redux
也实现了hooks
,留坑,有时间再更新吧。
用例源码
store.js
import { createStore, combineReducers } from 'redux'
const ageReducer = (state = {age: 18}, action) => {
switch (action.type) {
case 'increment':
return {...state, age: state.age + 1}
case 'decrement':
return {...state, age: state.age - 1}
default:
return state
}
}
const nameReducer = (state = {name: 'jerry'}, action) => {
switch (action.type) {
case 'changeName':
return {...state, name: action.payload.name}
default:
return state
}
}
const reducers = combineReducers({ageReducer, nameReducer});
const store = createStore(reducers)
export default store
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux'
import store from './store'
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
app.js
import React, { Profiler } from 'react';
import SagaTest from './components/redux/storeTest'
function App () {
return (
<div className="App">
<SagaTest gender="male"></SagaTest>
</div>
);
}
export default App;
src/components/redux/storeTest.js
import React from 'react'
import { connect } from 'react-redux'
class User extends React.Component {
constructor(props){
super(props)
}
handleIncrementClick = () => {
this.props.ageIncrement()
}
handleDecrementClick = () => {
this.props.ageDecrement()
}
handleNameChange = () => {
this.props.changeName({name: 'tom'})
}
render() {
console.log(this.props)
return <div>
姓名: {this.props.nameReducer.name}
<br />
年龄: {this.props.ageReducer.age}
<br />
<button onClick={this.handleIncrementClick}>+</button>
<button onClick={this.handleDecrementClick}>-</button>
<button onClick={this.handleNameChange}>改变名字</button>
</div>
}
}
const mapStateToProps = (state, props) => {
console.log(state)
return {...state, ...props}
}
// const mapDispatchToProps = (dispatch) => {
// return {
// ageIncrement: payload => dispatch({type: 'increment', payload}),
// ageDecrement: payload => dispatch({type: 'decrement', payload}),
// changeName: payload => dispatch({type: 'changeName', payload}),
// }
// }
const ConnectedUser = connect(mapStateToProps, {
ageIncrement(payload) {
return {type: 'increment', payload}
},
ageDecrement(payload) {
return {type: 'decrement', payload}
},
changeName(payload) {
return {type: 'changeName', payload}
}
})(User)
export default ConnectedUser
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 289211569@qq.com