react+redux 实战(一)-- 基础流程

2,119 阅读7分钟

工程框架可以使用项目构建工具yeoman构建,使用方法可自行搜索。我们使用的是react-webpack,到使用iconfont 的时候会可能报如下错误



可以将cfg文件夹下的default.js中的loaders部分中.(png|jpg|gif|woff|woff2|eot|ttf|svg)后面加上(\?[a-z0-9=\.]+)即可以解决,下面步入正题。

React部分

这部分应该就大都是react的基础了,稍微看过react文档就可以写出简单静态页面。一个js文件中的基本结构如下:

class Person extends React.Component {
  constructor (props) {
    super(props);
    this.state = { smiling: false };
  }
  handleClick(){
    this.setState({smiling: !this.state.smiling});
  };
  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },
  componentDidMount () {
    // React.getDOMNode()
  },
  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },
  smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }
  render () {
    return (
      
{this.props.name} {this.smilingMessage.bind(this)}
); }, } Person.defaultProps = { name: 'Guest' }; Person.propTypes = { name: React.PropTypes.string }; export default Person;

使用react最重要的应该是考虑如何安排组件

比如想要实现一个新闻列表,首先观察其由哪些组件构成。


显而易见,列表页包含两种组件

那么我们就需要创建一个容器组件,也就是ArticleList,其中包含两种展示组件GridArticle和GridKeyArticle

class ArticleList extends React.component{

    render() {
        const { articles, isFetching } = this.props;
        const articleNodes = [];

        articles.forEach(function(article, i){
            if(article.genre == 1){
                 //将数据key和article作为属性传递到子组件中
                articleNodes.push();
            }else{
                articleNodes.push();
            }
        }); 

        return (       
            
{articleNodes}
); } } //子组件 GridArticle class GridArticle extends React.Component { render() { //取出从父组件拿到的数据,这样方便分配和管理 const { article } = this.props; return (

{article.title}

{article.source} {article.praise_count} {article.comment_count} {Utils.smartDate(article.publish_time)}
); } }

向组件中传入数据时记得要加上key值,类似id,作为该组件的唯一标识。我们只需要将对应数据传入组件,在容器中引用组件变得再简单不过,这时候你应该开始好奇了,这些props数据从哪里来。

react应用中最重要的部分应该就是数据的流动和处理,这个我们选择了使用redux来帮助我们进行数据管理。

Redux部分

回顾基本流程:view直接触发dispatch;将action发送到reducer中后,根节点上会更新state,进而改变全局view。在整个redux流程中,action只是充当了一个类似于topic之类的角色,reducer会根据这个topic确定需要如何返回新的数据,一个reducer就对应着一个字段(理解这个是理解redux应用中数据结构的关键);数据的结构处理也从store中移到了reducer中。


createStore方法接收reducer函数和初始化的数据(currentState),并将这2个参数并保存在store中。
createStore时传入的reducer方法会在store的dispatch被调用的时候被调用,接收store中的state和action,根据业务逻辑修改store中的state;

说到这儿,不得不想一下这个问题:

Question:redux中的state和react中的state是什么关系?

Answer:没丁点关系。详细的可以参考这篇文章。redux中的state指的是全局状态树,简单点可以理解为一个数据库。其中的props就是数据库中的部分数据,这部分是需要作为属性传递到相应的组件中去的。而react中介绍的state就是一个在本组件中临时存储的状态变量。在使用了redux的应用中,就彻底抛弃react中的state吧,否则会污染全局状态,发生不可控的错误。

对于action和reducer部分,可以使用工具redux-actions,这样在Redux中也能使用Flux标准编写action,这样代码会简洁不少。下面是一个fetch请求的action和reducer代码:

//actions/articles.js
import { createActions } from 'redux-actions';

export const { fetchArticles } = createActions({
    FETCH_ARTICLES: async (timestamp = '20160829') => {
        try {
            let response = await fetch(`/interfaces/articles/articlemore/${timestamp}.json`);
            let articles = await response.json();
            return { timestamp, articles }
        } catch (err) {
            console.log(err);
        }
    }
});

//reducer/articles.js
import { handleActions } from 'redux-actions';
import { FETCH_ARTICLES } from '../actions/index.js';

export default handleActions({
    FETCH_ARTICLES: (state, action) => {
        let payload = action.payload;

        return Object.assign({}, state, {
            [payload.timestamp]: {
                isFecting: false,
                articles: payload.articles
            }
        });
    }
}, {});

PS: fetch 是未来异步的主要方式,可以看看这篇fetch文档

除此之外,注册store,并向其中传入combile之后的rootReducer和initialState,redux的工具就已基本建立完成。当然了,在redux中使用中间件也是比较常用的功能,我们可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等,它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点,更替的说是执行原本的dispatch(action)之前,类似这样middleware1(middleware2(middleware3(store.dispatch)))(action),所以我们的store部分就类似这样:

import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import createLogger from 'redux-logger';
import rootReducer from '../reducers/index.js';

const loggerMiddleware = createLogger();

const createStoreWidthMiddleware = applyMiddleware(
    promiseMiddleware,
    loggerMiddleware
)(createStore);

export default function store(initialState) {
    return createStoreWidthMiddleware(rootReducer, initialState);
}

那么我们我们在页面中如何使用这个redux呢?
先利用react-redux提供的Provider方法注入store将我们的react应用和redux连接起来:

//在根目录下使用provider包裹即可

import React from 'react';
import { Provider } from 'react-redux';
import App from './containers/App';
import store from './stores/index';

React.render(
    
, document.getElementById('app'));

然后呢?props那些变量和方法从哪儿导入,数据从如何获取?

这个需要使用react-redux提供的connect方法将我们需要的state中的数据(还可以加上actions中的方法)绑定到props中,在需要绑定数据的容器中:

//将全局的state映射到组件的props,相当于从store获取数据,
//一种reducer就对应数据库中的一个字段,所以根据需要传入的数据引用对应的reducer
function mapStateToProps(state){
    const { articlesReducer } = state;

    const {
        isFetchting,
        articles: articles
    } = articlesReducer['20160829'] || {
        isFetchting: true,
        articles: []
    };

    return {
        page: 1,
        articles: articles,
        isFetchting: isFetchting
    }
}

export default connect(mapStateToProps)(ArticleList);

这样在该容器中即可通过props拿到articles等数据,这样也就可以顺利的进行向后续组件中传递数据了。然而我们这只是相当于绑定了该组件是对应数据库中的这一部分的数据,如何获取呢?在组件渲染之后(或者之前,但是这样的话需要保证获取这些数据的动作及reducer操作与dom结构无关)去dispatch(fetchArticles)

componentDidMount(){ 
    const { dispatch } = this.props; 
    dispatch(fetchArticles());
 }

再次总结流程

那么在ArticleList的最终代码就是:

'use strict';

import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { fetchArticles } from '../../actions/index.js';

import GridArticle from '../../components/grid-article/index.js'
import GridKeyArticle from '../../components/grid-key-article/index.js'

require('./index.less');


class ArticleList extends React.Component {
    constructor(props){
        super(props)
    }

    componentDidMount(){
        const { dispatch } = this.props;

        dispatch(fetchArticles());
    }


    render() {
        const { articles, isFetching } = this.props;
        const articleNodes = [];

        articles.forEach(function(article, i){
            if(article.genre == 1){
                articleNodes.push();
            }else{
                articleNodes.push();
            }
        }); 

        return (
            
                
{articleNodes}
); } } ArticleList.displayName = 'ArticleList'; ArticleList.propTypes = { page: PropTypes.number, articles: PropTypes.array, isFetchting: PropTypes.bool, dispatch: PropTypes.func }; ArticleList.defaultProps = {}; // 将全局的state映射到组件的props,相当于从store获取数据 function mapStateToProps(state){ const { articlesReducer } = state; const { isFetchting, articles: articles } = articlesReducer['20160829'] || { isFetchting: true, articles: [] }; return { page: 1, articles: articles, isFetchting: isFetchting } } export default connect(mapStateToProps)(ArticleList);

思考:mapStateToProps方法与触发action关系

mapStateToProps方法是容器中配合redux提供的connect方法使用的

//这是上面推荐的文章中的部分代码

//将state.counter绑定到props的counter
function mapStateToProps(state) { 
  return { counter: state.counter }
}
//通过react-redux提供的connect方法将我们需要的state中的数据绑定到props上
export default connect(mapStateToProps)(Counter)

但是,在开发过程中发现,此时的state中都是那些reducer,回到fetch请求的例子,也就是state下我只能访问到state.articleReducer[timestamp],其实我们需要的是其返回的articles数据,但是却不能再继续通过.articles拿到,这样会报错,这个也是一直困惑我的地方。所以我们使用了比较low的解决办法。。。

function mapStateToProps(state,ownParams){
    const { articlesReducer } = state;

    const {
        isFetchting,
        articles: articles
    } = articlesReducer[ownParams.timestamp] || {
        isFetchting: true,
        articles: []
    };

    return {
        page: 1,
        articles: articles,
        isFetchting: isFetchting
    }
}

关于mapStateToProps如何优雅的返回数据,还望诸位不吝赐教。

既然mapStateToProps是将全局的state映射到组件的props,也就是相当于这个组件从store获取了数据,那么为什么还要使用dispatch这个fetch请求数据的action呢?就觉得两者都使用就仿佛说话累赘了么?

在这个页面容器中,我们同样写了这样的代码:

componentWillMount(){
        const { dispatch } = this.props;

        dispatch(fetchArticles());
    }

最后我的理解是:mapPropsToState方法是告诉reducer你要请求的部分数据去哪儿找,但是你得触发这个去找的动作,这个给你指明的方向才会有意义啊。所以在容器页面中,这两者不可或缺,dispatch(fetchArticles)执行,对应的reducer按着方向去获取相应的数据。