为什么需要redux
- 解决组件间数据传递的问题
redux的简单流程原理图:
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中间)
- 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;