react+redux实战(-)——基本流程

工程框架可以使用项目构建工具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 (
      <div onClick={this.handleClick.bind(this)}>
        {this.props.name} {this.smilingMessage.bind(this)}
      </div>
    );
  },
}

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(<GridArticle key={i} article={article} />);
            }else{
                articleNodes.push(<GridKeyArticle key={i} article={article} />);
            }
        }); 

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

        return ( 
            <Link to="/articles/1" className="com-grid-article clearfix" >
                <div className="grid-article-left">
                    <h2>{article.title}</h2>
                    <div className="ribbon">
                    <span>{article.source}</span>
                    <span className="iconfont icon-praise">{article.praise_count}</span>
                    <span className="iconfont icon-message">{article.comment_count}</span>
                    <span className="smart-date">{Utils.smartDate(article.publish_time)}</span>
                    </div>
                </div>
                <div className="grid-article-right">
                    <img src={article.banner_pic} className="imgcover"/>
                </div>
            </Link>
        );
    }
}

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

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

Redux部分

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

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

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

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

redux完整流程

一般应用中都会有多个reducer,redux推荐使用combineReducers方法将不同的reducer合并成一个最终的reducer,然后对这个reducer调用creatStore。合并后的reducer可以调用各个子reducer,并把它们的结果合并成一个state对象,state中的数据结构就是各个子reducer的集合,为了后边传递数据方便,我们可以给子reducer分配个key。

import { combineReducers } from 'redux';
import articleReducer from './article.js';
import articlesReducer from './articles.js';
import commentsReducer from './comments.js';

const rootReducer = combineReducers({
  //为子reducer设置了key值之后,可以在容器组件中比较轻易的绑定该部分对应的数据,下文会再次提到这点
    article:articleReducer,
    articles:articlesReducer,
    comments:commentsReducer
});

export default rootReducer;
//所以这时我们的state树的一级子树中有article,articles,comments三个字段。
//这样我们可以在对应容器中只传入该部分数据,
//减少了传递的数据大小和数据操作换乱导致错误的风险。

createStore方法接收reducer函数和初始化的数据(currentState),并将这2个参数并保存在store中。createStore时传入的reducer方法会在store的dispatch被调用的时候被调用,接收store中的state和action,根据业务逻辑修改store中的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,并向其中传入合并之后的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 createStoreWithMiddleware = applyMiddleware(
    promiseMiddleware,
    loggerMiddleware
)(createStore);

export default function store(initialState) {
    return createStoreWithMiddleware(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(
    <div>
        <Provider store={store()}>
            <App />
        </Provider>
    </div>,
    document.getElementById('app'));

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

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

//将全局的state映射到组件的props,相当于从store获取数据,
//一种reducer就对应数据库中的一个字段,所以根据需要传入的数据引用对应的reducer
function mapStateToProps(state,ownParams){
//比如获取对应id或者时间戳等参数的文章,可以通过ownParams参数传进来,为了方便,以下例子中都使用的是变量。
    const { articles } = state;
    const {
        isFetchting,
        articles: articles
    } = articles['20160829'] || {
        isFetchting: true,
        articles: []
    };

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

export default connect(mapStateToProps)(ArticleList);

这多说几句:

在上面的例子代码中把articlesReducer的内容拆开了,看起来很low的样子。其实也可以把整个articles对象作为一个整体传递,在render函数中接收后再单独使用,这儿就会变得美观了。

function mapStateToProps(state){
    return{
        articlesList:state.articles[20160829]
        //这儿最远只能取到reducer字段
    }
}

但是有一点要注意:仍以上面代码为例,数据articles(改个名便于区分)叫做articlesList,其结构中包括isFetching和articles两个字段,如果我们在render函数中直接使用articlesList.articles.forEach(xxxxxx),很有可能就会报错,说这个articles不存在。明明它就是articlesList中的字段啊,怎么会不存在呢?这种基本都是因为该部分数据比如articlesList还没获取到。那就又有疑问了,我们在ComponentWillMount中已经提前dispatch(fetchArticles)了啊,注意!我们的请求都是异步的,所以在render执行的时候我们的数据还没获取到,所以为了程序的顺利进行,我们需要一点保护措施:

//如果articlesList获取到了,再使用其子数据
if(articlesList){
    articlesList.articles.forEach(xxxx);
}
//所以我们的代码虽然看起来ugly一点,但是不会发生因为某个值undefined而导致程序报错的错误

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

componentWillMount(){ 
    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)
    }

    componentWillMount(){
        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(<GridArticle key={i} article={article} />);
            }else{
                articleNodes.push(<GridKeyArticle key={i} article={article} />);
            }
        }); 

        return (
                <div className="com-article-list">
                    {articleNodes}
                </div>
        );
    }
}

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方法使用的

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

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

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

        dispatch(fetchArticles());
    }

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

写在最后:使用webpack-dev-server,提升开发效率

众所周知,webpack提供了强大的模块打包功能,同样,热加载功能在开发阶段也是我们的得力助手,使用webpack-dev-server启动一个web服务器,监听文件修改,可通知浏览器自动刷新。网上关于webpack的使用很多,这儿我就不多介绍了,关键是我也讲不清(害羞脸)。。。还要学习啊!!!

下一篇:
react+redux实战(二)----搭建express将应用连接到数据库mongodb

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容