react全家桶搭一个博客 - 复盘

文件目录

image.png

api 与 配置文件 与一个入口HTML

  • api里就是mock的json数据,模拟从数据库中取数据。
  • webpack.config.js 中,webpack主要就加载了css的loader和babel的loader。并且制定了打包的入口文件
  • package.json 就没什么好说的了
  • babelrc里面是babel的配置文件,配置了项目需要的es6和react的编译插件
  • HTML react渲染到它的身上,需要引入webpack打包好的bundle文件

src目录

这个目录下就是react组件和一些redux相关的文件。按功能分成了不同的子文件夹。

layouts

这里面放了的UI框架组件:
||-- Frame.js
||-- Nav.js
这两个文件是任路由跳转,我自岿然不动。 所以,可以把他们做成dumb组件。

1.Frame

Frame是UI的整体布局,其中包括了两个section,分别放着<Nav>和另一个由router控制渲染内容的section,由this.props.children传进来,根据路由决定渲染文章列表,还是文章详情。默认是文章列表(Home)

import React, {Component} from 'react';
import Nav from './Nav';

class Frame extends Component {
    render() {
        return (
            <div className="frame">
                <section className="header">
                    <Nav/>
                </section>
                <section className="container">
                    {this.props.children} {/* 渲染的默认是Home*/}
                </section>
            </div>
        )
    }
}

export default Frame;
2.Nav

Nav组件用了react-router提供的链接功能组件Link,就是一个指向主页的链接

import React, {Component} from 'react';
import { Link } from 'react-router';

class Nav extends Component {
    render() {
        return (
            <nav>
                <Link to="/">Home</Link>
            </nav>
        )
    }
}

export default Nav;

components目录

这是dumb组件之家。放到这个目录里的组件对背后控制他们的力量一无所知。通过props传给他们数据,传什么渲染什么。还有一个包工头PreviewListRedux,来生成控制他们的action。这个黑心包工头也负责和smart组件接头。

PreviewListRedux.js
//先来看看黑心包工头PreviewListRedux.js
const initialState = {
    loading: true,
    error: false,
    articleList: []
}

const LOAD_ARTICLES = 'LOAD_ARTICLES';
const LOAD_ARTICLE_SUCCESS = 'LOAD_ARTICLE_SUCCESS';
const LOAD_ARTICLE_ERROR = 'LOAD_ARTICLE_ERROR';

export function loadArticles() {
    return {
        types: [LOAD_ARTICLES, LOAD_ARTICLE_SUCCESS, LOAD_ARTICLE_ERROR],
        url: '/api/articles.json',
    }
}

export default function preViewList(state = initialState, action) {
    switch (action.type){
        case LOAD_ARTICLES: {
            return {
                ...state,
                loading: true,
                error: false,
            };
        }
        case LOAD_ARTICLE_SUCCESS:{
            return {
                ...state,
                loading: false,
                error:false,
                articleList:action.payload
            }
        }
        case LOAD_ARTICLE_ERROR:{
            return {
                ...state,
                loading: false,
                error:true,
            }
        }
        default:
            return state;
    }
}

首先,我们整个博客暂时需要维护的state就只有三个:

  • loading:就是当加载文章到获取文章渲染之间,渲染loading
  • error : 没取到文章,哦豁
  • articleList:请求到了文章列表,渲染出来

action也就是围绕着加载文章设计的。加载文章,加载成功,加载失败。这个地方的loadArticles()是一个产生异步请求的action creator,所以它的types是一个数组。这是由redux-composable-fetch这个中间件定义的。这两个方法将被整合到HomeRedux.js中,交给上层smart组件Home.js,由它作为props分发给PreviewList。

傻瓜父子PreviewList & Preview

PreviewList 挂载完成之后就会请求文章列表。所以控制它的有loading,error,和请求成功的articleList数组,同时,它也需要能够在componentDidMount的时候,请求文章列表,所以还需要交给他loadArticles()。

import React, {PropTypes, Component} from 'react';
import Preview from './Preview';

class PreviewList extends Component {
    static propTypes = {
        loading: PropTypes.bool,
        articleList: PropTypes.arrayOf(PropTypes.object),
        error: PropTypes.bool,
        loadArticles: PropTypes.func,
    };

    componentDidMount() {
        this.props.loadArticles();
    }


    render() {
        const {loading, error, articleList} = this.props;

        if (error) {
            return <p className="message">Oops, something is wrong</p>
        }
        if (loading) {
            return <p className="message">Loading</p>
        }



        return (
            <div>
                {articleList.map(item => (
                <Preview {...item} key={item.id} push={this.props.push}/>
            ))}
            </div>)
    }
}


export default PreviewList

Preview组件除了展示请求到的数据之外,还需要有一个路由跳转的功能。这个功能是通过redux-router-redux的push方法提供的。也是通过smart组件Home.js通过属性传给Preview的。有了push方法,Preview组件就可以绑定点击事件,实现路由跳转到详情页了。

import React, {Component} from 'react';
import './Preview.css'

class Preview extends Component {
    static propTypes = {
        title: React.PropTypes.string,
        link: React.PropTypes.string,
        push: React.PropTypes.func,
    };

    handleNavigate(id, e) {
        //阻止原生链接跳转
        e.preventDefault();
        //使用react-router-redux的方法进行跳转,方便更新store
        // 遇到一个错误,原来是跳转地址没有写对
        this.props.push(`/detail/${id}`)
    }


    render() {
        return (
            <acticle className="article-preview-item">
                <h1 className="title">
                    <a href={`/detail/${this.props.id}`} onClick={this.handleNavigate.bind(this, this.props.id)}>
                        {this.props.title}
                    </a>
                </h1>
                <span className="date">{this.props.date}</span>
                <p className="desc">{this.props.description}</p>
            </acticle>
        )


    }
}

export default Preview

view目录

这个目录下就放的是通过connect包装的smart组件以及他们控制的dumb组件集成起来的reducer和action(*Redux.js)
HomeRedux里引入了dumb组件需要的reducer和actions。
Home组件存放的是用connect包装后的smart组件。接受的state为home.list组件树,同时也dispatch两个方法:listAction(用于dispatch获得文章列表的action)和push(用于路由转换,由react-router-redux提供)

import React, {Component} from 'react';
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import PreviewList from '../components/Home/PreviewList';
import {actions} from './HomeRedux'
import {push} from 'react-router-redux'

class Home extends Component {
    render() {
        return (
            <div>
                <h1>Home1</h1>
                <PreviewList
                    {...this.props.list}
                    {...this.props.listActions}
                    push={this.props.push}
                />
            </div>
        )
    }
}

// connect(mapStateToProps,mapDispatchToProps)
export default connect(state => {
    return {
        list: state.home.list, //取出整个Redux状态树中 home.list分支作为当前组件的Props
    }
}, dispatch => {
    return {
        listActions: bindActionCreators(actions, dispatch),
        push:bindActionCreators(push,dispatch)
    }
})(Home);

views 下面还包括了一个简单的Detail组件,用于展示点击的文章详情,非常简陋。

import React, {Component} from 'react';

class Detail extends Component {
    render() {
        return (
            <h1>Detail</h1>
        )
    }
}

export default Detail;

Route 目录

这个目录下放整个项目的路由框架。此时我们的路由其实非常简单,就是一个博文列表主页,和每一个博文的详细页面。这两个部分公用layouts,所以就用一个Frame把这两个部分包裹起来

import React from 'react';
import { Router, Route, IndexRoute } from 'react-router';

import Frame from '../layouts/Frame';
import Home from '../views/Home';
import Detail from '../views/Detail';

const routes = browserHistory => (
    <Router history={browserHistory}>
        <Route path="/" component={Frame}>
            <IndexRoute component={Home} />
            <Route path="/detail/:id" component={Detail} />
        </Route>
    </Router>
);

export default routes;

redux目录

这里是redux的配置部分。

reducer.js

这里就是将子state集成成一个state,目前的状态树只有来自HomeRedux这一个枝。

import home from '../views/HomeRedux';

export default {
    home,
};
DevTools.js

配置了需要的redux开发工具。LogMonitor和DocMonitor

import React from 'react';

import { createDevTools } from 'redux-devtools';

import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';

const DevTools = createDevTools(
    <DockMonitor toggleVisibilityKey='ctrl-h'
                 changePositionKey='ctrl-q'>
        <LogMonitor theme='tomorrow' />
    </DockMonitor>
);

export default DevTools;
configStore.js

使用Node.js环境变量process.env.NODE_ENV判断生产环境和开发环境,加载不同的配置文件,差别就是是否隐藏devTool。

    module.exports = require('./configureStore.prod');
} else {
    module.exports = require('./configureStore.dev');
}
configStore.dev.js
import {createStore, combineReducers, compose, applyMiddleware} from 'redux';
import { routerReducer } from '../../node_modules/react-router-redux/lib/reducer'
import { routerMiddleware } from 'react-router-redux'
import { browserHistory } from 'react-router'

import ThunkMiddleWare from 'redux-thunk'
import rootReducer from './reducers'
import createFetchMiddleware from 'redux-composable-fetch';
import DevTools from './DevTools';

const FetchMiddleware = createFetchMiddleware({
    afterFetch({ action, result }) {
        return result.json().then(data => {
            return Promise.resolve({
                action,
                result: data,
            });
        });
    },
});


const finalCreateStore = compose(
    applyMiddleware(
        ThunkMiddleWare,
        FetchMiddleware,
        routerMiddleware(browserHistory)),
    DevTools.instrument()
)(createStore)

const reducers = combineReducers(Object.assign({}, rootReducer, {routing: routerReducer}));

export default function configureStore(initialState) {
    const store = finalCreateStore(reducers, initialState);
    return store
}

这里配置了Fetch中间件,使用中间件扩充了store,也集成了需要的reducer。最后创建了最终的store。


最后总结

跟着《深入react技术栈》的第五章搭出来的一个简单的react+redux以及一些它们的插件们的前端应用,使用mock数据做伪后台。之前遇到了因为使用的npm包版本问题导致的各种跑不动的问题,之后还是都解决了。深深的感受到了被版本支配的恐惧。之后要做的就是完善这个系统,做成一个完整的有CRUD功能的blog,最好能够加进MongoDB数据库,而不是用mock提供数据交互。还有页面可以美化一下。

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

推荐阅读更多精彩内容