redux 入门到实践

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

内容简介:之前没太理解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 入门到实践》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

高效前端:Web高效编程与优化实践

高效前端:Web高效编程与优化实践

李银城 著 / 机械工业出版社 / 2018-3-15 / 89.00元

这不是一本单纯讲解前端编程技巧的书,而是一本注重思想提升和内功修炼的书。 全书以问题为导向,精选了前端开发中的34个疑难问题,从分析问题的原因入手,逐步给出解决方案,并分析各种方案的优劣,最后针对每个问题总结出高效编程的最佳实践和各种性能优化的方法。 全书共7章,内容从逻辑上大致可以分为两大类: 第一类,偏向实践,围绕HTML、CSS、JavaScript等传统前端技术,以及PW......一起来看看 《高效前端:Web高效编程与优化实践》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具