03【React再造之旅】redux在React中的基础使用

写在前面

同样的,这篇文章继续参考学习"Mr_大木"大佬的《React-Redux基础(二):React里面如何使用redux,redux修改操作》这篇文章后的一片笔记。

这篇文章在上一节的基础之上,来给大家介绍下如何在基于React的项目中使用Redux。因为是实际操作篇,所以就没有前面的一些概念介绍的东西,我们直接开始操作环节。

react中使用redux

一、新建react项目demo

1、在介绍开始之前我们首先要创建一个react框架的项目demo。在这里你可以试用react脚手架像上文一样创建一个新的项目demo,你也可以将上一节中使用的demo代码里的,关于redux那些代码注释后继续使用上一节的代码。

因为上一节的代码里我们只安装了一个redux,其余的都是没有改变的,所以这里我们直接使用上一节的代码,将index.js里面关于redux部分的代码注释即可,即恢复到react项目demo最初始的状态,如下:

2、不管是新创建的项目demo,还是上节使用过的代码,创建或恢复初始状态后,在项目根目录通过以下命令启动项目,如下:

npm start

二、react项目中使用redux

1、在使用redux之前,首先要在项目中安装redux,在react项目中使用redux的时候,除了安装redux之外,还要安装一个react-redux,它的作用类似于一个桥梁,将我们的react项目和单独的redux进行连接,使得我们可以在react中去使用redux。所以通过以下命令全装这俩插件依赖:

npm install redux react-redux --save-dev

2、安装完成之后可以看到两个插件当前的版本号:

3、接下来我们先在index.js文件里引入createStore来创建一个store,因为redux提供的全局状态管理是要在整个应用中来使用的,所以我们在index.js文件里操作,后期我们再来优化。首先是创建store,代码如下:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
/***********创建store开始*************/
import { createStore } from 'redux';
​
function default_reducer(state = {
    name: 'X北辰北',
    url: 'www.xbeichen.cn'
}, action) {
    switch(action.type) {
        case 'setName':
            return {
                ...state,
                name: action.value
            };
        default:
            return state;
    };
};
​
let store = createStore(default_reducer);
/***********创建store结束*************/
​
ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

上述代码中创建store的过程跟上一节介绍的是没有差别的。那我们创建的store怎么和我们的react应用绑定起来呢,这里就需要使用react-redux插件里提供的一些方法了。

4、引入react-redux插件中的Provider,使用它来将我们的store和react应用绑定,这里要注意的是,Provider组价跟我们的Router路由组件类似,是要放在应用代码的最外层的,如下:

import { Provider } from 'react-redux';
​
/****省略其余代码***/
​
ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
);

上述代码里使用了Provider组件来将我们新建的store和react应用做了绑定,这个组件中传进去了我们新建的store作为组件中store属性的值,这样就完成了store和react的绑定。

5、绑定完成之后,我们在组件里应该怎么来使用store里的数据呢,这就需要react-redux插件中的另一个方法,叫connect。在组件中使用connect方法,即可将这个组件和我们的store进行连接,进而这个组件可以使用redux里面store里存储的数据,代码如下:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { connect } from 'react-redux';  //引入connect
​
class App extends React.Component {
    render() {
        return (
            <div className="App">
                <span>App组件</span>
            </div>
          );
    }
}
​
//export default App;
export default connect()(App);   //将组件和redux进行连接

因为是在App这个组件里我们来使用redux中的数据,所以我们在App组件代码里引入了connect方法,从而使这个组件和redux进行了连接。上面代码可以看到,我们是用connect的时候,这个方法执行了两次,所以有两个小括号,这是因为第一次执行的时候,它需要做合并和类似于dispatch的操作,第二次才会将我们的组件包裹进去,进行和redux里面的store连接。

6、connect()方法第一次执行的时候,会进行合并和dispatch操作,所以在它第一次执行的时候需要传入两个参数:第一个参数是一个函数,用来合并store里的数据和你组件自身的props,所以在这里可以看到它并不会将你组件自身的state和redux中的store合并,而是将组件自身的props和redux中的store合并,这是因为redux推荐的一种做法:如果某些数据仅仅是你这个组件自身使用的话,那就通过组件内部的state去维护即可,没有必要将数据往redux里去放。所以它并不会合并你组件的state,而是合并组件的props,然后将合并后的数据放在你的props里面,就是说它给你返回了一个props;第二个参数是一个json,说白了就是我们要传入reducer的action。在这里我们先来看下connect函数的第一个参数的用法:

export default connect(function() {}, {})(App);
​
//或者用箭头函数
export default connect(() => {}, {})(App);

connect函数的第一个参数是一个用来合并store和组件自身props的函数,它需要两个参数,第一个参数是state,第二个参数是props。这里的state参数是redux传过来的store,props参数是组件自身的props,然后函数体里面用来合并这俩参数的代码我们是用Object对象的assign方法,代码如下:

export default connect((state, props) => {
    return Object.assign({}, props, state);    //定义了一个空对象
}, {})(App);

上述代码是将store和props进行了合并,并且合并的时候以state里的数据为准,所以将state放在了props的后面,但是在它俩之前定义了一个空对象,这是因为组件自身的props是只读的,我们没法用state里的数据去覆盖props,而是通过一个空对象,将props里的数据先复制到这个空对象,然后用state去覆盖这个新定义的对象,进而达到合并的目的。那合并了之后怎么用里面的数据呢,我们来看看具体的使用:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { connect } from 'react-redux';
​
class App extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'GeoV'
        }
    }
    render() {
​
        console.log(this.props);    //输出合并后的数据
        console.log(this.state);    //输出组件自身的state数据
​
        return (
            <div className="App">
                <span>App组件</span>
                <h5>名称:{this.props.name}</h5>
            </div>
          );
    }
}
​
//export default App;
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {})(App);

上述代码里我们通过this.props.XXX的方式来在组件中使用合并后的数据,同时为了区分,我们还定义了组件自身的state数据,将合并后的数据和组件自身的state数据在render方法里进行了打印,可以看到是完全没有问题的,符合我们的预期。

以上就是connect函数的第一个参数的基础使用,到现在为止,我们的组件从redux取值是没有问题,在这里只有一个组件,我们或许看不到redux的优势,那我们新建两个组件,来继续看看它的优势在哪。

7、在src目录下新建components目录,然后在里面新建两个组件,如下:

ComponentOne组件代码:

import React from 'react';
import './ComponentOne.css';
​
class ComponentOne extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'ComponentOne'
        }
    }
    render() {
        return (
            <div className="ComponentOne">
                <span>ComponentOne组件</span>
            </div>
          );
    }
}
​
export default ComponentOne;

ComponentTwo组件代码:

import React from 'react';
import './ComponentTwo.css';
​
class ComponentTwo extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'ComponentTwo'
        }
    }
    render() {
        return (
            <div className="ComponentTwo">
                <span>ComponentTwo组件</span>
            </div>
          );
    }
}
​
export default ComponentTwo;

然后在这两个组件中分别引入connect之后,将其和redux进行连接,代码如下:

//组件一的代码
import React from 'react';
import './ComponentOne.css';
import { connect } from 'react-redux';
​
class ComponentOne extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'ComponentOne'
        }
    }
    render() {
        return (
            <div className="ComponentOne">
                <span>ComponentOne组件</span>
                <h5>组件一中获取的name:{this.props.name}</h5>
            </div>
          );
    }
}
​
//export default ComponentOne;
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {})(ComponentOne);
​
​
//组件二的代码
import React from 'react';
import './ComponentTwo.css';
import { connect } from 'react-redux';
​
class ComponentTwo extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'ComponentTwo'
        }
    }
    render() {
        return (
            <div className="ComponentTwo">
                <span>ComponentTwo组件</span>
                <h5>组件二中获取的name:{this.props.name}</h5>
                <h5>组件二中获取的url:{this.props.url}</h5>
            </div>
          );
    }
}
​
//export default ComponentTwo;
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {})(ComponentTwo);

最后在App组件里面引入这俩组件,如下:

import React from 'react';
import logo from './logo.svg';
import './App.css';
import { connect } from 'react-redux';
import ComponentOne from './components/componentOne/ComponentOne';   // 引入两个组件
import ComponentTwo from './components/componentTwo/ComponentTwo';
​
class App extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'GeoV'
        }
    }
    render() {
​
        console.log(this.props);    //输出合并后的数据
        console.log(this.state);    //输出组件自身的state数据
​
        return (
            <div className="App">
                <span>App组件</span>
                <h5>名称:{this.props.name}</h5>
                <ComponentOne />     //使用两个组件
                <ComponentTwo />
            </div>
          );
    }
}
​
//export default App;
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {})(App);

由上面结果可看到,我们使用了redux之后,组件之间就不存在复杂的父子组件传值或者兄弟组件传值这些问题了,主要组件要想获取全局的某个数据,直接使用connect将其和redux中的store做连接,然后去获取store里的数据即可。以上就是关于组件对于redux中的值的获取操作,也是connect方法中第一个参数的介绍。

8、接下来看看connect中的第二个参数的介绍。第二个参数之前说过是一个json,它主要是用来封装action的,当我们涉及到需要修改redux中的数据时就需要这个参数。上一节我们知道修改store中的数据是通过store的dispatch方法去做的,但是在这里我们不必再写dispatch,因为react-redux它帮我们封装了,我们只需要在connect的第二个参数里写方法即可,如下:

import React from 'react';
import './ComponentTwo.css';
import { connect } from 'react-redux';
​
class ComponentTwo extends React.Component {
    constructor(...args) {
        super(...args);
​
        this.state = {
            name: 'ComponentTwo'
        }
    }
​
    //修改名称函数
    _changeName=()=> {
        this.props.setName(this.refs.name.value);    //去调用connect函数中第二个参数中定义的函数setName
    }
​
    render() {
        return (
            <div className="ComponentTwo">
                <span>ComponentTwo组件</span>
                <h5>组件二中获取的name:{this.props.name}</h5>
                <h5>组件二中获取的url:{this.props.url}</h5>
​
                <input type='text' ref='name' />
                <input type='button' value='修改名称' onClick={this._changeName} />
            </div>
          );
    }
}
​
//export default componentTwo;
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {
    setName(nameValue) {
        return {
            type: 'setName',    //必须要有一个type属性,跟reducer中的action的type属性对应
            value: nameValue
        }
    }
})(ComponentTwo);

由上述代码可见,我们想要修改redux中的值时,只需要在connect方法中定义第二个参数即可,这个参数是一个json格式,里面我们只需要直接写函数就可以,它已经替我们封装了dispatch方法,我们只需要在定义的函数里返回action就可以了,返回的时候还是跟上文介绍的一样,必须携带一个type属性,这个属性值必须跟reducer里面的action中的type属性一致。

9、以上就是关于整个redux在react项目中的应用介绍了,包括react组件中使用redux中的数据、在react组件中修改redux中的数据等。

三、一些优化操作

以上的文章给大家简单介绍了一下如何在react项目中使用redux的问题,但是上述的过程中存在几个地方需要优化,如果你不优化的话没有任何问题,功能照样满足、代码照样执行,但是它不美观,也不太友好,所以这部分给大家介绍下如何去优化它们,我们先看看要优化那几部分:

  • 首先要优化的就是index.js文件,因为我们的store创建这些全部放在了index.js里面,这样违背了这个文件最初的用意;
  • 其次就是要优化reducer里面action的字符串,因为后期涉及到可能会更改某一个type属性,这样一来更改的地方有点多,不符合编码规范;
  • 然后就是reducer的优化,目前我们只有一个reducer函数,也就是说当前是一个单一数据对象,如果我们将数据全部扔到这一个reducer里面,那这个reducer会异常庞大,我们要分割这个reducer函数;
  • 最后就是关于App这个组件在index.js里面的使用问题,因为目前的做法,当我们同时使用Provide和Router去包裹应用的时候会有问题。

了解了以上存在的问题之后,我们接下来就一个个的进行逐步优化。

1、首先是index.js文件里面内容过多的问题。其实在这里说的内容过多就是我们将store的创建放在了index.js里面而已,所以我们需要将这个过程从index.js里面提出来,单独进行。

1.1、在src目录下新建一个store目录,然后在此目录下新建一个index.js文件,如下:

1.2、新建完成之后,我们在项目根目录的index.js文件里面删除掉关于redux的引用,然后将里面的reducer定义和store创建代码全都剪切到新建的store目录下的index.js文件里面,如下:

//store目录下的index.js文件中的代码
import { createStore } from 'redux';
​
function default_reducer(state = {
    name: 'X北辰北',
    url: 'www.xbeichen.cn'
}, action) {
    switch(action.type) {
        case 'setName':
            return {
                ...state,
                name: action.value
            };
        default:
            return state;
    };
};
​
let store = createStore(default_reducer);
​
export default store;     //在最后要添加这一行代码,将新建的store导出

1.3、然后在根目录下的index.js里面引入新建的store目录下的index.js文件,并将其绑定到react应用上面,这时候根目录下的index.js文件里的代码如下所示:

//项目根目录下的index.js里的代码
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import store from './store/index';        //导入store
​
ReactDOM.render(
  <Provider store={store}>       //做绑定
    <App />
  </Provider>,
  document.getElementById('root')
);

1.4、这样一来我们就将store的创建从index.js文件里面移动出去了,此时的index.js文件看起来清爽了很多。

2、接下来优化一下reducer里面action的字符串问题,说白了就是下面这些代码:

    switch(action.type) {
        case 'setName':     //这行代码里的setName字符串
            return {
                ...state,
                name: action.value
            };
        default:
            return state;
    };

因为上述代码里的setName字符串是写死在代码里的,当我们想要更改的话,就要去这个代码里面来进行更改,如果这个文件里有不同reducer函数里都有setName这个字符串,那是一件很麻烦的事情,所以我们将它配置成一个变量,在另一份文件里单独维护,具体做法如下。

2.1、在store目录下新建一个action.js文件,里面将我们所用的action的字符串导出即可,如下:

export const SET_NAME = 'setName';
export const SET_URL = 'setUrl'; 

2.2、然后修改store目录下的index.js文件代码,如下:

import { createStore } from 'redux';
import { SET_NAME, SET_URL } from './action';   //先导入要使用的常量
​
function default_reducer(state = {
    name: 'X北辰北',
    url: 'www.xbeichen.cn'
}, action) {
    switch(action.type) {
        case SET_NAME:     //用常量去替换掉代码里的字符串
            return {
                ...state,
                name: action.value
            };
        case SET_URL:     //用常量去替换掉代码里的字符串
            return {
                ...state,
                url: action.value
            };
        default:
            return state;
    };
};
​
let store = createStore(default_reducer);
​
export default store;

同样的,用到setName字符串的地方都可以这样做,比如在组件二里面也用到了这个字符串,那我们可以同样的去优化,关键代码如下:

import { SET_NAME } from '../../store/action';    //先是导入这个常量
​
/***中间过程省略***/
​
export default connect((state, props) => {
    return Object.assign({}, props, state);
}, {
    setName(nameValue) {
        return {
            type: SET_NAME,    //然后在是使用字符串的地方去替换即可
            value: nameValue
        }
    }
})(ComponentTwo);

2.3、以上就是优化action里的字符串过程,这样一来后期修改字符串的话直接去维护action.js文件就可以,不用再回到reducer函数里面去修改了。

3、接下来我们介绍下如何分割reducer,因为一个真实的项目应用里面不可能只有reducer函数,肯定是分割后的多个reducer函数,所以这就需要我们将分割后的多个reducer函数在创建store之前进行合并。

3.1、reducer的分割其实是我们开发的时候,自己定义多个reducer函数来形成的,如下:

function default_reducer(state = {
    name: 'X北辰北',
    url: 'www.xbeichen.cn'
}, action) {
    switch(action.type) {
        case SET_NAME:
            return {
                ...state,
                name: action.value
            };
        case SET_URL:
            return {
                ...state,
                url: action.value
            };
        default:
            return state;
    };
};
​
function second_reducer(state = {
    age: 25,
    phone: '2250685378'
},action) {
    switch(action.type) {
        case SET_AGE:
            return {
                ...state,
                age: action.value
            };
        default:
            return state;
    };
};

像上述的代码一样,我们定义了两个reducer,那这个时候我们如果去直接创建store会有问题的,因为你不知道要给createStore去传哪一个reducer,所以在创建store之前我们首先要将这两个分割的reducer合并。

3.2、合并的时候使用redux提供的combineReducers方法进行合并,如下:

import { createStore, combineReducers } from 'redux';       //引入combineReducers
import { SET_NAME, SET_URL, SET_AGE } from './action';
​
function default_reducer(state = {
    name: 'X北辰北',
    url: 'www.xbeichen.cn'
}, action) {
    switch(action.type) {
        case SET_NAME:
            return {
                ...state,
                name: action.value
            };
        case SET_URL:
            return {
                ...state,
                url: action.value
            };
        default:
            return state;
    };
};
​
function second_reducer(state = {
    age: 25,
    phone: '2250685378'
},action) {
    switch(action.type) {
        case SET_AGE:
            return {
                ...state,
                age: action.value
            };
        default:
            return state;
    };
};
​
let cobineReducer = combineReducers({   //合并reducer
    first: default_reducer,    //合并的时候指定一个命名空间
    second: second_reducer
});
​
let store = createStore(cobineReducer);   //用合并后的reducer去创建store
​
export default store;

3.3、上述代码将两个分割后的reducer进行了合并,并且用合并后的reducer去创建了store,但是在我们组建里面使用store的时候,不能像以前一样通过this.props.XXX的形式去访问了,而是要加上合并时指定的一个命名空间,如下:

3.4、以上就是分割reducer和合并reducer并且使用store里面的数据的介绍。

4、关于redux和router共同使用时会有冲突的问题,是由于两个插件都在占用props这个属性造成的,一般是由于这种写法造成的,如下:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import store from './store/index';
​
ReactDOM.render(
  <Provider store={store}>     //Provider和Router共同使用
      <Router>
        <App />
      </Router>
  </Provider>,
  document.getElementById('root')
);

如果是上面这种写法,当我们在路由跳转的时候会在App这个组件里会报错。这个时候我们这样改写代码即可:

import { Provider } from 'react-redux';
import { BrowserRouter as Router, Route } from 'react-router-dom';    //引入Route
​
ReactDOM.render(
  <Provider store={store}>
      <Router>
        <Route component={App} />     //使用Route包裹一下App组件即可
      </Router>
  </Provider>,
  document.getElementById('root')
);

其实上述的冲突问题一般不会遇到,只有在index.js文件里同时使用Provider和Router去包裹App组件时才有可能会发生,上面已经给了解决方法。因为平时我们的路由配置一般不会写在index.js这里面,所以一般是不会遇到冲突问题的。

总结

以上就是这篇文章的全部内容了。文章从开始介绍react应用里如何使用redux,并且给大家介绍了如何去优化的问题,当然,本文只是一个简单的介绍,大家在项目开发时会遇到很多未知的问题,遇到问题的话记得滴滴博主,我们一起来学习。

附:

demo代码地址如下:

https://gitee.com/XuQianWen/redux_basic

猜你喜欢

转载自blog.csdn.net/qq_35117024/article/details/106429060