redux 入门到实践

栏目: IOS · Android · 发布时间: 6年前

内容简介:之前没太理解redux,在使用时总是照葫芦画瓢,看项目里别人如何使用,自己就如何使用,这一次彻底学习了下官方文档,记录。在学习redux初时,有三个概念需要了解。类型是一个

之前没太理解redux,在使用时总是照葫芦画瓢,看项目里别人如何使用,自己就如何使用,这一次彻底学习了下官方文档,记录。

在学习redux初时,有三个概念需要了解。

  • action
  • reducer
  • store

Action

类型是一个 Object 更改 storestate 的唯一方法,它通过 store.dispatchaction 传到 store

一个简单的 action

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
复制代码
dispatch(addTodo(text))
复制代码

Reducer

根据 action ,来指定store中的state如何改变。

store

存储state

store.getState();
复制代码
getState()
dispatch(action)
subscribe(listener)

更新store的步骤

1.创建action,action中必须要有type 2.创建reducer,根据action中的type来更新store中的state 3.初始化store

理解不可变性

在reducer更新state时,不能改变原有的state,只能重新创建一个新的state。这里提供了几个方法可以来创建一个不同的对象。

  • 使用immutable-js创建不可变的数据结构
  • 使用JavaScript库(如Loadsh)来执行不可变的操作
  • 使用ES6语法执行不可变操作

之前并不了解 immutable-js ,所以还是使用es6的语法来执行不可变操作。

let a = [1, 2, 3];              // [1, 2, 3]
let b = Object.assign([], a);   // [1, 2, 3]

// a !== b
复制代码

上面和下面是相同的

// es6语法
let a = [1,  2, 3]; // [1, 2, 3]
let b = [...a];     // [1, 2, 3]

// a !== b
复制代码

初始化store

在创建store时要将注意传入开发者 工具 相关参数

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger'
import api from '../middleware/api'
import rootReducer from '../reducers'
import DevTools from '../containers/DevTools'

const configureStore = preloadedState => {
  const store = createStore(
    rootReducer,
    preloadedState,
    compose(
      applyMiddleware(thunk, api, createLogger()),
      DevTools.instrument()
    )
  )
  
  // ..省略相关代码
  return store
}

export default configureStore
复制代码

createStore

参数

  • reducer (Function,必选):用于返回新的 state ,给出当前的 stateaction
  • preloadedState (Any,可选):初始化 state , 你可以选择将其指定为通用应用程序中的服务器状态,或者还原以前序列化的用户会话,如果使用 combineReducers 生成 reducer ,则它必须是一个普通对象,其形状与传递给它的键相同。否则,您可以自由地传递 reducer 只要能够理解。
  • enhancer (Function,可选),可以指定它使用第三方功能增强 store ,例如中间件等等。随 Redux 一起提供的 enhancer 只有 applyMiddleware() ,传入的enhancer只能是一个。

返回值

(Store): 保存应用完整 state 的对象,只要 dispatching actions 才能改变它的 state 。你可以用 subscribestate 的改变来更新UI。

Tips

  • 最多创建一个 store 在一个应用当中,使用 combineReducers 来创建根 reducer
  • 你可以选择状态的格式,可以选择普通对象或类似 Immutable ,如果不确定,先从普通对象开始
  • 如果state是个普通对象,请确定永远不要改变它,例如从 reducers 返回对象时,不要使用 Object.assign(state, newData) ,而是返回 Object.assign({}, state, newData) 。这样就不会覆盖以前的状态,或者使用 return {...state, ...newData}
  • 要使用多个 enhancer 可以使用 compose()
  • 创建 store 时, Redux 会发送一个虚拟的 action 用来初始化 storestate ,初始化时第一个参数未定义,那么store的state会返回 undefined

Enhancer

增强器

Middleware

官方文档 中有提到,中间件是用来包装 dispatch

这里看一个官方的例子,从这个例子中就可以看到,传入参数是 action ,随后可以对这个 action 进行一些操作。

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'

function logger({ getState }) {
  return next => action => {
    console.log('will dispatch', action)

    // Call the next dispatch method in the middleware chain.
    const returnValue = next(action)

    console.log('state after dispatch', getState())

    // This will likely be the action itself, unless
    // a middleware further in chain changed it.
    return returnValue
  }
}

const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
复制代码

使用 applyMiddleware 参数可以使多个中间件,最后返回的是一个 enhancer

相关提示

  • 有一些中间件可能只在某个特定环境下使用,比如日志中间件,可能在生成环境就不需要了。需要注意引用。
let middleware = [a, b]
if (process.env.NODE_ENV !== 'production') {
  const c = require('some-debug-middleware')
  const d = require('another-debug-middleware')
  middleware = [...middleware, c, d]
}

const store = createStore(
  reducer,
  preloadedState,
  applyMiddleware(...middleware)
)
复制代码

Provider与connect

需要额外安装

yarn add react-redux
复制代码

provider和connect必须一起使用,这样 store 可以作为组件的 props 传入。关于 Providerconnect ,这里有一篇淘宝的文章可以看下Provider和connect

大致使用如下,在 root container 当中,会加入 Provider

const App = () => {
  return (
    <Provider store={store}>
      <Comp/>
    </Provider>
  )
};
复制代码

在根布局下的组件当中,需要使用到 connect

mapStateToProps

connect 方法第一个参数 mapStateToProps 是可以将 store 中的 state 变换为组件内部的 props 来使用。

const mapStateToProps = (state, ownProps) => {
  // state 是 {userList: [{id: 0, name: '王二'}]}
  // 将user加入到改组件中的props当中
  return {
    user: _.find(state.userList, {id: ownProps.userId})
  }
}

class MyComp extends Component {
  
  static PropTypes = {
    userId: PropTypes.string.isRequired,
    user: PropTypes.object
  };
  
  render(){
    return <div>用户名:{this.props.user.name}</div>
  }
}

const Comp = connect(mapStateToProps)(MyComp);

复制代码

mapDispatchToProps

connect 方法的第二个参数,它的功能是将 action 作为组件的 props

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    increase: (...args) => dispatch(actions.increase(...args)),
    decrease: (...args) => dispatch(actions.decrease(...args))
  }
}

class MyComp extends Component {
  render(){
    const {count, increase, decrease} = this.props;
    return (<div>
      <div>计数:{this.props.count}次</div>
      <button onClick={increase}>增加</button>
      <button onClick={decrease}>减少</button>
    </div>)
  }
}

const Comp = connect(mapStateToProps, mapDispatchToProps)(MyComp);

复制代码

利用props使用store

import { setUser } from 'action';
// 在使用了connect的组件中 store在它的props当中
const { dispatch } = this.porps;

const user = ...;
// 直接分发设置user
dispatch(setUser(user));
复制代码

异步场景下更新store

  • Thunk middleware
  • redux-promise
  • redux-observable
  • redux-saga
  • redux-pack
  • 自定义...

Redux-thunk

在没有使用 Redux-thunk 之前,当我们需要改变store中的state,只能使用使用 dispath 传入 action 的形式,这里有个官方的例子能够说明它的使用场景。

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

// Note: this API requires redux@>=3.1.0
const store = createStore(
  rootReducer,
  applyMiddleware(thunk)
);

function fetchSecretSauce() {
  return fetch('https://www.google.com/search?q=secret+sauce');
}

// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express “facts” and not the “async flow”.

function makeASandwich(forPerson, secretSauce) {
  return {
    type: 'MAKE_SANDWICH',
    forPerson,
    secretSauce
  };
}

function apologize(fromPerson, toPerson, error) {
  return {
    type: 'APOLOGIZE',
    fromPerson,
    toPerson,
    error
  };
}

function withdrawMoney(amount) {
  return {
    type: 'WITHDRAW',
    amount
  };
}

// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100));

// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?

// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.

function makeASandwichWithSecretSauce(forPerson) {

  // Invert control!
  // Return a function that accepts `dispatch` so we can dispatch later.
  // Thunk middleware knows how to turn thunk async actions into actions.

  return function (dispatch) {
    return fetchSecretSauce().then(
      sauce => dispatch(makeASandwich(forPerson, sauce)),
      error => dispatch(apologize('The Sandwich Shop', forPerson, error))
    );
  };
}

// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!

store.dispatch(
  makeASandwichWithSecretSauce('Me')
);

// It even takes care to return the thunk’s return value
// from the dispatch, so I can chain Promises as long as I return them.

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});
复制代码

thunk 可以让我们在 dispatch 执行时,可以传入方法,而不是原本的 action

我们可以看一下 thunk 的源码,当 action 是方法时,它会将 action 进行返回。

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    // action的类型是方法时,放回action
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;
复制代码

经过这样,我们就可以理解为什么在上述的官方例子当中可以这么使用。

store.dispatch(
  makeASandwichWithSecretSauce('My wife')
).then(() => {
  console.log('Done!');
});
复制代码

makeASandwichWithSecretSauce 实际会返回 fetch().then() 返回值,而 fetch().then() 返回的是Promise对象。

Redux-saga

在开始讲述 saga 以前,先讲下与它相关的ES6语法 Generator 函数

function* helloWorldGenerator() {
  // 可以将yield看成return,只不过yield时,还能继续
  yield 'hello';
  yield 'world';
  return 'ending';
}

var hw = helloWorldGenerator();

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
复制代码

异步Generator函数

这里有2个方法,一个是通过回调写的,一个是通过generator来写的

fs.readFile('/etc/passwd', 'utf-8', function (err, data) {
  if (err) throw err;
  console.log(data);
});
复制代码
function* asyncJob() {
  // ...其他代码
  var f = yield readFile(fileA);
  // ...其他代码
}
复制代码

官方文档的一个例子如下

function render() {
  ReactDOM.render(
    <Counter
      value={store.getState()}
      onIncrement={() => action('INCREMENT')}
      onDecrement={() => action('DECREMENT')}
      onIncrementAsync={() => action('INCREMENT_ASYNC')} />,
    document.getElementById('root')
  )
}
复制代码

在使用 saga 时,都会建立一个 saga.js ,其余的都是和普通的redux一样,需要创建 action``reducerstore

import { delay } from 'redux-saga'
import { put, takeEvery } from 'redux-saga/effects'

// ...

// Our worker Saga: 将执行异步的 increment 任务
export function* incrementAsync() {
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

// Our watcher Saga: 在每个 INCREMENT_ASYNC action spawn 一个新的 incrementAsync 任务
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
复制代码

当主动触发了 onIncrementAsync 回调之后,就会发送一个 INCREMENT_ASYNC ,在 saga 接受到这个action时候,就会 incrementAsync ,在这个方法当中会延迟1000毫秒,随后put(类似于dispatch)发送一个type为 increment 的事件,在 reducer 当中,可以根据这个 action 做出对 storestate 进行操作。

我们可以看到这里yield的使用更像是await。

两种其实都是通过不同的异步方式对store进行操作。thunk本身其实没有异步的功能,但是它能够拓展dispath,加入传入的是一个异步方法,那就让它能够具有异步的功能。

设置开发者工具

在官方Example当中有提到,创建一个 DevTools 文件, ctrl-h 打开显示toggle, ctrl-w 改变开发者工具的位置

import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

export default createDevTools(
  <DockMonitor toggleVisibilityKey="ctrl-h"
               changePositionKey="ctrl-w">
    <LogMonitor />
  </DockMonitor>
)

复制代码

然后将该组件放在根目录

import React from 'react'
import PropTypes from 'prop-types'
import { Provider } from 'react-redux'
import DevTools from './DevTools'
import { Route } from 'react-router-dom'
import App from './App'
import UserPage from './UserPage'
import RepoPage from './RepoPage'

const Root = ({ store }) => (
  <Provider store={store}>
    <div>
      <Route path="/" component={App} />
      <Route path="/:login/:name"
             component={RepoPage} />
      <Route path="/:login"
             component={UserPage} />
      <DevTools />
    </div>
  </Provider>
)

Root.propTypes = {
  store: PropTypes.object.isRequired,
}

export default Root

复制代码

最后在 createStore 时需要传入

import DevTools from '../devtool'

const store = createStore(
    rootReducer,
    preloadedState,
    compose(
      applyMiddleware(thunk),
      DevTools.instrument()
    )
  )
复制代码

效果图如下

redux 入门到实践

实战

我们需要的要使用redux需要

  • 建立action
  • 建立对应reducer
  • 创建store

同时,为了方便

  • 需要有Provider

项目目录

项目目录如下所示

redux 入门到实践

action/index.js

创建一个 action ,用于告知 reducer ,设置用户信息,增加一个 type ,让 reducer 根据 type 来更新 store 中的 state

export const TYPE = {
  SET_USER: 'SET_USER'
};

export const setUser = (user) => ({
  type: 'SET_USER',
  user
});
复制代码

reducer/user.js

创建一个关于 userreducer

import {
  TYPE
} from '../action'

const createUser = (user) => user;

const user = (state = {}, action) => {
  console.log(action);
  switch (action.type) {
    case TYPE.SET_USER:
      // 根据type来更新用户信息
      return {...state, ...createUser(action.user)};
    default:
      return state;
  }
}

export {
  user
}


复制代码

reducers/index.js

reducer ,用于将其他不同业务的 reducer 合并。

import { combineReducers } from 'redux';

import { user } from './user';

export default combineReducers({
  user
});
复制代码

store/config-store.dev.js

store 中有不同的初始化 store 的方法,dev中有开发者工具,而pro中没有。这里做了个区分。

import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'
import DevTools from '../devtool'

const configureStore = preloadedState => {
  const store = createStore(
    rootReducer,
    preloadedState,
    compose(
      applyMiddleware(thunk),
      DevTools.instrument()
    )
  )

  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      store.replaceReducer(rootReducer)
    })
  }

  return store
}

export default configureStore

复制代码

store/configure-store.prod.js

import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from '../reducers'

const configureStore = preloadedState => createStore(
  rootReducer,
  preloadedState,
  applyMiddleware(thunk)
)

export default configureStore
复制代码

store/configure-store.js

根据不同环境读取不同的初始化store的文件。

if (process.env.NODE_ENV === 'production') {
  module.exports = require('./configure-store.prod')
} else {
  module.exports = require('./configure-store.dev')
}

复制代码

devtool/index.js

开发者组件的配置文件。

import React from 'react'
import { createDevTools } from 'redux-devtools'
import LogMonitor from 'redux-devtools-log-monitor'
import DockMonitor from 'redux-devtools-dock-monitor'

export default createDevTools(
  <DockMonitor toggleVisibilityKey="ctrl-h"
               changePositionKey="ctrl-w">
    <LogMonitor />
  </DockMonitor>
)

复制代码

index.js

在index.js中初始化store

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import configureStore from './store/store/configure-store';

const store = configureStore();

ReactDOM.render(

  <App store={store}/>

, document.getElementById('root'));
registerServiceWorker();

复制代码

app.jsx

在根文件中,创建provider

import React, { Component } from 'react'
import './App.css'
import './reset.css'
import 'antd/dist/antd.css'
import Auth from './pages/auth'
import Star from './pages/star/star'
import { BrowserRouter, Route, Redirect } from 'react-router-dom'
import DevTools from './store/devtool'
import { Provider } from 'react-redux'


class App extends Component {
  constructor(props) {
    super(props)

    this.onClickAuth = this.onClickAuth.bind(this)
  }

  onClickAuth() {}

  /**
   * 渲染开发者工具
   */
  renderDevTools() {
    if (process.env.NODE_ENV === 'production') {
      return null;
    }
    return (<DevTools />)
  }

  render() {
    return (
      <Provider store={this.props.store}>
        <div className="App">
          <BrowserRouter basename="/">
            <div>
              <Route exact path="/" component={Auth} />
              <Route path="/auth" component={Auth} />
              <Route path="/star" component={Star} />
              { this.renderDevTools() }
            </div>
          </BrowserRouter>
        </div>
      </Provider>
    )
  }
}

export default App

复制代码

更新用户信息

import React, { Component } from 'react';
import './star.scss';
import globalData from '../../utils/globalData';
import StringUtils from '../../utils/stringUtils';
import { List, Avatar, Row, Col } from 'antd';
import Api from '../../utils/api';
import Head from '../../components/Head/Head';
import ResInfo from '../../components/resInfo/resInfo';
import ControlList from '../../components/control/control-list';
import StarList from '../../components/star-list/star-list';
import Eventbus from '@/utils/eventbus.js';
import { connect } from 'react-redux';
import { setUser } from '../../store/action';

class Star extends Component {
  constructor(props) {
    super(props);

    this.state = {
      tableData: [],
      originTableData: [],
      userInfo: {},
      rawMdData: ''
    };
  }

  componentDidMount() {
    this.getUserInfo();
  }

  componentWillUnmount() {
  }

  getUserInfo() {
    Api.getAuthenticatedUser()
      .then(data => {
        this.handleGetUserInfoSuccessResponse(data);
      })
      .catch(e => {
        console.log(e);
      });
  }

  /**
   * 获取完用户信息
   */
  handleGetUserInfoSuccessResponse(res) {
    this.setState({
      userInfo: res.data
    });
    this.getStarFromWeb();
    this.refs.controlList.getTagsFromWeb();

    const { dispatch } = this.props;
    // 更新用户信息
    dispatch(setUser(this.state.userInfo));
  }

  // ...省略一些代码
  
  render() {
    return (
      <div className="star">
        <Head
          ref="head"
          head={this.state.userInfo.avatar_url}
          userName={this.state.userInfo.login}
        />
        <Row className="content-container">
          <Col span={3} className="control-list-container bg-blue-darkest">
            <ControlList
              ref="controlList"
              onClickRefresh={this.onClickRefresh}
              onClickAllStars={this.onClickAllStars}
              onClickUntaggedStars={this.onClickUntaggedStars}
            />
          </Col>
          <Col span={5} className="star-list-container">
            <StarList
              tableData={this.state.tableData}
              onClickResItem={this.onClickResItem.bind(this)}
            />
          </Col>
          <Col span={16}>
            <div className="md-container">
              <ResInfo resSrc={this.state.rawMdData} />
            </div>
          </Col>
        </Row>
      </div>
    );
  }
}

const mapStateToProps = (state, ownProps) => ({
  user: state.user
});

export default connect(mapStateToProps)(Star);

复制代码

以上所述就是小编给大家介绍的《redux 入门到实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective Java: Second Edition

Effective Java: Second Edition

Joshua Bloch / Addison-Wesley / 2008-05-28 / USD 54.99

Written for the working Java developer, Joshua Bloch's Effective Java Programming Language Guide provides a truly useful set of over 50 best practices and tips for writing better Java code. With plent......一起来看看 《Effective Java: Second Edition》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换