redux 超神篇

为什么需要redux

  • 解决组件间数据传递的问题

redux的简单流程原理图:

redux 超神篇_第1张图片
例1:简单流程原理图
redux 超神篇_第2张图片
例2:简单流程原理图

redux 和 react-redux区别

redux 超神篇_第3张图片
redux 和 react-redux区别

单独使用redux

npm i redux react-redux

  • index.js(入口文件)
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore } from 'redux';
import reducer from './reducers/counter';

const store = createStore(reducer); // 创建store,store其实就是reducer集合

// store.subscribe(() => console.log("State updated!", store.getState()));

const render = () => {
  ReactDOM.render(
     store.dispatch({ type: "INCREMENT" }) } // store通过调用dispatch方法,传递一个action(action就是一个对象)参数,从而触发调用reducer
      onDecrement={ () => store.dispatch({ type: "DECREMENT" }) }
      value={ store.getState() }
    />, document.getElementById('root'));
};

render();

store.subscribe(render); // store 要监听的函数

registerServiceWorker();
  • App.js
import React, { Component } from 'react';
import PropTypes from 'prop-types';

class App extends Component {
  render() {
    return (
      

{ this.props.value }

); } } App.propTypes = { value: PropTypes.number.isRequired, onIncrement: PropTypes.func.isRequired, onDecrement: PropTypes.func.isRequired } export default App;

reducers/counter.js

const counter = (state = 0, action = {}) => {
  switch(action.type) {
    case 'INCREMENT':
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default: return state;
  }
}

export default counter;

react+redux

npm i react-redux

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore } from 'redux';
import reducer from './reducers/counter';
import { Provider } from 'react-redux';

const store = createStore(reducer);

// store.subscribe(() => console.log("State updated!", store.getState()));

ReactDOM.render(
   // 通过Provider注入store
    
  ,
  document.getElementById('root')
);

registerServiceWorker();
  • constants/index.js(定义常量)
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

actions/index.js(抽离定义actions)

import { INCREMENT, DECREMENT } from '../constants';

export const increment = () => {
  return {
    type: INCREMENT
  }
};

export const decrement = () => {
  return {
    type: DECREMENT
  }
};
  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';

class App extends Component {
  render() {
    return (
      

{ this.props.counter }

); } } const mapStateToProps = (state) => { // state参数,相当于 store.getState(),把state转成props return { counter: state }; }; export default connect(mapStateToProps)(App); // 通过connect,引入store,通过第一个参数mapStateToProps,注入state。也就是说把state传递给我component。

使用combineReducers组合多个reducer

  • reducers/user.js(新建user reducer)
const user = (state = "redux111", action = {}) => {
  switch(action.type) {
    default: return state;
  }
}

export default user;

  • reducers/index.js(新建index reducer,使用combineReducers合并导入的各个reducer)
import { combineReducers } from 'redux';

import counter from './counter';

import user from './user';

const rootReducer = combineReducers({
  counter,
  user
});

export default rootReducer;

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore } from 'redux';
import rootReducer from './reducers'; // !!!使用的是rootReducer
import { Provider } from 'react-redux';

const store = createStore(rootReducer);

// store.subscribe(() => console.log("State updated!", store.getState()));

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

registerServiceWorker();

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';

class App extends Component {
  render() {
    return (
      

{ this.props.counter }

); } } const mapStateToProps = (state) => { // console.dir(state); return { counter: state.counter // 使用的时候要先获取对应的key,console就知道了。 }; }; App.propTypes = { counter: PropTypes.number.isRequired } export default connect(mapStateToProps)(App);

component获取dispatch的几种方法

方法一:connect没有传递第二个参数,直接在component的render方法中获取

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { increment } from './actions'; // 这里获取的函数,调用返回一个对象

class App extends Component {
  render() {
    const { dispatch } = this.props; // 方法一:直接在props中获取dispatch函数
    return (
      

{ this.props.counter }

// 同时给action传递参数

); } } const mapStateToProps = (state) => { return { counter: state.counter }; }; App.propTypes = { counter: PropTypes.number.isRequired } export default connect(mapStateToProps)(App); // 没有传递第二个参数
  • actions/index.js(获取参数)
export const increment= (obj) => {
  return {
    type: INCREMENT,
    obj
  }
};

  • reducers/counter.js(使用参数)
const counter = (state = 1, action = {}) => {
  switch (action.type) {
    case 'INCREMENT':
      console.dir(action.obj); // !!!
      return state + 1;
    case 'DECREMENT':
      return state - 1;
    default: return state;
  }
}

export default counter;

方法二:connect传递第二个参数

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { increment } from './actions';

class App extends Component {
  render() {
    const { aa } = this.props; // 自定义的key,aa其实就是一个函数,可以传递参数
    return (
      

{ this.props.counter }

); } } const mapStateToProps = (state) => { return { counter: state.counter }; }; const mapDispatchToProps = (dispatch) => { return { aa: (name) => { dispatch(increment(name)) } } }; App.propTypes = { counter: PropTypes.number.isRequired } export default connect(mapStateToProps, mapDispatchToProps)(App); // 传递第二个参数

方法三:使用bindActionCreators

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { increment, decrement } from './actions';
import { bindActionCreators } from 'redux';

class App extends Component {
  render() {
    const { increment, decrement } = this.props;
    return (
      

{this.props.counter}

); } } const mapStateToProps = (state) => { return { counter: state.counter }; }; const mapDispatchToProps = (dispatch) => { // bindActionCreators的好处是如果需要传递参数的时候,不用写。关于它的说明,可以在官网查看 // http://www.redux.org.cn/docs/api/bindActionCreators.html // 方式一写法: // return { // increment: bindActionCreators(increment, dispatch), // decrement: bindActionCreators(decrement, dispatch) // } // 方式二写法: return bindActionCreators({ increment, decrement }, dispatch); }; App.propTypes = { counter: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired } // 第一、二种方式: export default connect(mapStateToProps, mapDispatchToProps)(App);

方法四:直接传,这种方式用得比较多

  • App.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { increment, decrement } from './actions';

class App extends Component {
  render() {
    const { increment, decrement } = this.props;
    return (
      

{this.props.counter}

); } } const mapStateToProps = (state) => { return { counter: state.counter }; }; App.propTypes = { counter: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired } export default connect(mapStateToProps, { increment, decrement })(App); // 在第二个参数,直接传对应的action函数

方法四改进,如果有很多action的时候,那就麻烦了

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import * as types from './actions'; // 很多action的时候,把所有的导出
import { bindActionCreators } from 'redux';

class App extends Component {
  render() {
    const { increment, decrement } = this.props; // 这里同样可以直接拿到所有的action
    return (
      

{this.props.counter}

); } } const mapStateToProps = (state) => { return { counter: state.counter }; }; const mapDispatchToProps = (dispatch) => { return bindActionCreators(types , dispatch); // 这里直接传递types }; App.propTypes = { counter: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, decrement: PropTypes.func.isRequired } export default connect(mapStateToProps, mapDispatchToProps)(App);

使用@connect装饰器

由于create-react-app并不支持es6 @ 装饰器的语法,这里有可以babel解决,也可以使用custom-react-scripts这个库替换create-react-app原来的核心库react-scripts

官网:
https://github.com/kitze/custom-react-scripts

安装替换:
npm uninstall --save react-scripts;
npm install --save custom-react-scripts;

使用:
在项目根目录添加  .env 配置文件。
支持@装饰器,使用babel的为配置:REACT_APP_DECORATORS=true

其他常见的配置见官网。

提示:传递state的时候,不要把整个对象都传递到component,因为component会频繁的更新,有性能问题

不好的:不要把整个对象user传过来
@connect(state => ({ 
   user: state.user,
   messages: state.messages
}))

好的:用到什么传什么
@connect(state => ({ 
   user_name: state.user.name,
   last_message: state.messages[state.messages.length-1]
}))

  • App.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import * as types from './actions';
import { bindActionCreators } from 'redux';

const mapStateToProps = (state) => {
  return {
    counter: state.counter
  };
};

const mapDispatchToProps = (dispatch) => {
  return bindActionCreators(types, dispatch);
};

@connect(mapStateToProps, mapDispatchToProps)
class App extends Component {
  static propTypes = {
    counter: PropTypes.number.isRequired,
    increment: PropTypes.func.isRequired,
    decrement: PropTypes.func.isRequired
  };

  render() {
    const { increment, decrement } = this.props;
    return (
      

{ this.props.counter }

); } } export default App;

中间件 Middleware(是在action和reducer中间)

redux 超神篇_第4张图片
中间件
  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import { createStore, applyMiddleware } from 'redux'; // 通过 applyMiddleware 
import rootReducer from './reducers';
import { Provider } from 'react-redux';

const logger = store => next => action => {
  console.log('dispatching', action);
  let result = next(action); // 表示去执行下一个中间件
  console.log('next state', store.getState());
  return result;
};

const error = store => next => action => {
  try {
    next(action) // 表示去执行下一个中间件,如果没有下一个中间件了,就去执行reducer。
  } catch(e) {
    console.log('error ' + e);
  }
};

// 多个箭头函数的理解
// const logger = function(store) {
//   return function(next) {
//     return function(action) {
//       console.log('dispatching', action);
//       let result = next(action);
//       console.log('next state', store.getState());
//       return result;
//     }
//   }
// }

const store = createStore(rootReducer, {}, applyMiddleware(logger, error));

// store.subscribe(() => console.log("State updated!", store.getState()));

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

registerServiceWorker();

  • reducers/counter.js
const counter = (state = 1, action = {}) => {
  switch(action.type) {
    case 'INCREMENT':
      throw new Error('error in INCREMENT') // 仅仅为了说明中间件执行顺序
      // return state + 1;
    case 'DECREMENT':
      return state - 1;
    default: return state;
  }
}

export default counter;

结果

中间件 redux-logger 打印日志

npm i --save redux-logger

使用:
import logger from 'redux-logger';

const store = createStore(rootReducer, {}, applyMiddleware(logger));

中间件 redux-thunk 处理异步

解决action返回函数报错问题。

  • index.js
import thunk from 'redux-thunk';

const store = createStore(rootReducer, {}, applyMiddleware(logger, thunk));

  • actions/index.js
import { INCREMENT, DECREMENT } from '../constants';

export const increment = () => { 
  return dispatch => { // dispatch这个是形参,参数
    setTimeout(() => {
      dispatch({ // 这个是调用reducer
        type: INCREMENT
      });
    }, 2000);
  };
};

ajax

只处理请求成功后的

  • components/User.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { get_user } from '../actions';

class User extends Component {
  render() {
    const { get_user, user } = this.props;
    return (
      

{ user.email }

); } } const mapStateToProps = (state) => { return { user: state.user }; }; export default connect(mapStateToProps, { get_user })(User);
  • actions/index.js
    npm i axios
import axios from 'axios';
import { FETCH_USER_SUCCESS } from '../constants';

export const get_user = () => {
  return dispatch => {
    axios.get("https://randomuser.me/api/")
      .then(res => {
        // dispatch触发reducer,调用fetch_user(res.data.results[0])返回action对象
        dispatch(fetch_user(res.data.results[0])); 
      })
      .catch(error => {
        console.log(error);
      })
  };
};

export const fetch_user = (user) => {
  return {
    type: FETCH_USER_SUCCESS,
    user
  }
};

  • reducers/user.js
import { FETCH_USER_SUCCESS } from '../constants';

const user = (state = {}, action = {}) => {
  switch(action.type) {
    case FETCH_USER_SUCCESS:
      return action.user
    default: return state;
  }
}

export default user;

进阶:处理失败和正在请求的情况

  • components/User.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { get_user } from '../actions';

class User extends Component {
  render() {
    const { get_user } = this.props;

  // 这里获取的是通过mapStateToProps,获取的user对象 
    const { error, isFetching, user } = this.props.user1;

    let data;

    if (error) {
      data = error;
    } else if (isFetching) {
      data = "Loading...";
    } else {
      data = user.email;
    }

    return (
      

{ data }

); } } const mapStateToProps = (state) => { return { user1: state.user }; }; export default connect(mapStateToProps, { get_user })(User);
  • actions/index.js
import axios from 'axios';
import { FETCH_USER_SUCCESS, FETCH_USER_REQUEST, FETCH_USER_FAILURE } from '../constants';

export const get_user = () => {
  return dispatch => {
    dispatch(fetch_user_request()) // ajax触发之前,正在加载中...
    axios.get("https://randomuser.me/api/")
      .then(res => {
        dispatch(fetch_user(res.data.results[0])); // 成功
      })
      .catch(error => {
        dispatch(fetch_user_failure(error.response.data)); // 失败
      })
  };
};

export const fetch_user_failure = (error) => {
  return {
    type: FETCH_USER_FAILURE,
    error
  };
};

export const fetch_user = (user) => {
  return {
    type: FETCH_USER_SUCCESS,
    user
  }
};

export const fetch_user_request = () => {
  return {
    type: FETCH_USER_REQUEST
  }
};

  • reducers/user.js
import { FETCH_USER_SUCCESS, FETCH_USER_REQUEST, FETCH_USER_FAILURE } from '../constants';

const initialState = {
  isFetching: false,
  error: null,
  user: {}
};
// state 初始化为对象
const user = (state = initialState, action = {}) => {
  switch(action.type) {
    case FETCH_USER_SUCCESS:
      return {
        isFetching: false,
        error: null,
        user: action.user
      };
    case FETCH_USER_REQUEST:
      return {
        isFetching: true,
        error: null,
        user: {}
      }
    case FETCH_USER_FAILURE:
      return {
        isFetching: false,
        error: action.error,
        user: {}
      };
    default: return state;
  }
}

export default user;

  • constants/index.js
export const INCREMENT = "INCREMENT";
export const DECREMENT = "DECREMENT";

// user
export const FETCH_USER_SUCCESS = "FETCH_USER_SUCCESS";
export const FETCH_USER_REQUEST = "FETCH_USER_REQUEST";
export const FETCH_USER_FAILURE = "FETCH_USER_FAILURE";

使用redux-promise-middleware简化action,该插件会衍生出几种状态的action

官网:https://github.com/pburtchaell/redux-promise-middleware/blob/master/docs/introduction.md
npm i redux-promise-middleware -s

  • index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import rootReducer from '../reducers';

  const store = createStore( rootReducer, {}, applyMiddleware(thunk, promise()));

  • actions/index.js
import axios from 'axios';
import { LOAD_USER } from '../constants';

// 通过 redux-promise-middleware 插件,返回
export const get_user = () => {
  return {
    type: LOAD_USER,
    // payload: axios.get("https://randomuser.me/api/") 
    payload: {
      promise: axios.get("https://randomuser.me/api/")
    }
  };
};

  • reducers/user.js
import { LOAD_USER_FULFILLED, LOAD_USER_PENDING, LOAD_USER_REJECTED } from '../constants';

const initialState = {
  isFetching: false,
  error: null,
  user: {}
};

const user = (state = initialState, action = {}) => {
  switch(action.type) {
    case LOAD_USER_FULFILLED:
      return {
        isFetching: false,
        error: null,
        user: action.payload.data.results[0]
      };
    case LOAD_USER_PENDING:
      return {
        isFetching: true,
        error: null,
        user: {}
      }
    case LOAD_USER_REJECTED:
      return {
        isFetching: false,
        error: action.payload.response.data,
        user: {}
      };
    default: return state;
  }
}

export default user;

调试插件

  • 谷歌安装插件 redux-devtools
  • 同时,应该在项目也安装 redux-devtools-extension 包

npm i redux-devtools-extension -D

import { composeWithDevTools } from 'redux-devtools-extension';

const store = createStore(rootReducer, {}, composeWithDevTools(applyMiddleware(logger, thunk, promise())));

// 说明:composeWithDevTools直接包围中间件即可,详见官网

开发和生产环境加载不同的store配置文件

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import { Provider } from 'react-redux';

import configureStore from './store/configureStore'; // 获取store配置文件

const store = configureStore(); // 函数调用

// store.subscribe(() => console.log("State updated!", store.getState()));

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

registerServiceWorker();

  • store/configureStore.js
if (process.env.NODE_ENV === 'production') {
  module.exports = require('./configureStore.prod');
} else {
  module.exports = require('./configureStore.dev');
}

说明:根据 process.env.NODE_ENV 区分加载哪个配置文件;通过module.exports导出,require获取相应内容。

  • store/configureStore.dev.js
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducers';

const configureStore = (preloadedState) => {
  const store = createStore(
    rootReducer,
    preloadedState,
    composeWithDevTools(applyMiddleware(logger, thunk, promise()))
  );

  return store;
};

export default configureStore; // 导出是一个函数,则需要调用后,才返回结果store。

说明:开发环境需要logger和调试插件redux-devtools-extension


  • store/configureStore.prod.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import rootReducer from '../reducers';

const configureStore = (preloadedState) => {
  const store = createStore(
    rootReducer,
    preloadedState,
    applyMiddleware(thunk, promise())
  );

  return store;
};

export default configureStore;

// 说明:生产环境不需要logger和调试插件。

create-react-app redux hmr (热模块加载)

热模块加载(或者说是热更新)就是当你在开发环境修改代码后,不用刷新整个页面即可看到修改后的效果。

解决:详见
https://github.com/facebook/create-react-app/issues/2317

  • index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

import { Provider } from 'react-redux';

import configureStore from './store/configureStore';

const store = configureStore();

// store.subscribe(() => console.log("State updated!", store.getState()));

ReactDOM.render(
  
    
  ,
  document.getElementById('root')
);

if (module.hot) {
  module.hot.accept('./App', () => { // 注意根据实际项目的路径,跟import App from './App' 一致
    ReactDOM.render(
      
        
      ,
      document.getElementById('root')
    );
  })
}

registerServiceWorker();

  • configureStore.dev.js(开发环境的store配置)
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import thunk from 'redux-thunk';
import promise from 'redux-promise-middleware';
import { composeWithDevTools } from 'redux-devtools-extension';
import rootReducer from '../reducers';

const configureStore = (preloadedState) => {
  const store = createStore(
    rootReducer,
    preloadedState,
    composeWithDevTools(applyMiddleware(logger, thunk, promise()))
  );

  if (process.env.NODE_ENV !== "production") {
    if (module.hot) {
      module.hot.accept('../reducers', () => { // 注意这里的reducers路径,跟 import rootReducer from '../reducers'  一致
        store.replaceReducer(rootReducer)
      })
    }
  }

  return store;
};

export default configureStore;

你可能感兴趣的:(redux 超神篇)