react全家桶搭一個(gè)博客 - 復(fù)盤

文件目錄

image.png

api 與 配置文件 與一個(gè)入口HTML

  • api里就是mock的json數(shù)據(jù),模擬從數(shù)據(jù)庫(kù)中取數(shù)據(jù)。
  • webpack.config.js 中,webpack主要就加載了css的loader和babel的loader。并且制定了打包的入口文件
  • package.json 就沒(méi)什么好說(shuō)的了
  • babelrc里面是babel的配置文件,配置了項(xiàng)目需要的es6和react的編譯插件
  • HTML react渲染到它的身上,需要引入webpack打包好的bundle文件

src目錄

這個(gè)目錄下就是react組件和一些redux相關(guān)的文件。按功能分成了不同的子文件夾。

layouts

這里面放了的UI框架組件:
||-- Frame.js
||-- Nav.js
這兩個(gè)文件是任路由跳轉(zhuǎn),我自巋然不動(dòng)。 所以,可以把他們做成dumb組件。

1.Frame

Frame是UI的整體布局,其中包括了兩個(gè)section,分別放著<Nav>和另一個(gè)由router控制渲染內(nèi)容的section,由this.props.children傳進(jìn)來(lái),根據(jù)路由決定渲染文章列表,還是文章詳情。默認(rèn)是文章列表(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} {/* 渲染的默認(rèn)是Home*/}
                </section>
            </div>
        )
    }
}

export default Frame;
2.Nav

Nav組件用了react-router提供的鏈接功能組件Link,就是一個(gè)指向主頁(yè)的鏈接

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組件之家。放到這個(gè)目錄里的組件對(duì)背后控制他們的力量一無(wú)所知。通過(guò)props傳給他們數(shù)據(jù),傳什么渲染什么。還有一個(gè)包工頭PreviewListRedux,來(lái)生成控制他們的action。這個(gè)黑心包工頭也負(fù)責(zé)和smart組件接頭。

PreviewListRedux.js
//先來(lái)看看黑心包工頭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;
    }
}

首先,我們整個(gè)博客暫時(shí)需要維護(hù)的state就只有三個(gè):

  • loading:就是當(dāng)加載文章到獲取文章渲染之間,渲染loading
  • error : 沒(méi)取到文章,哦豁
  • articleList:請(qǐng)求到了文章列表,渲染出來(lái)

action也就是圍繞著加載文章設(shè)計(jì)的。加載文章,加載成功,加載失敗。這個(gè)地方的loadArticles()是一個(gè)產(chǎn)生異步請(qǐng)求的action creator,所以它的types是一個(gè)數(shù)組。這是由redux-composable-fetch這個(gè)中間件定義的。這兩個(gè)方法將被整合到HomeRedux.js中,交給上層smart組件Home.js,由它作為props分發(fā)給PreviewList。

傻瓜父子PreviewList & Preview

PreviewList 掛載完成之后就會(huì)請(qǐng)求文章列表。所以控制它的有l(wèi)oading,error,和請(qǐng)求成功的articleList數(shù)組,同時(shí),它也需要能夠在componentDidMount的時(shí)候,請(qǐng)求文章列表,所以還需要交給他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組件除了展示請(qǐng)求到的數(shù)據(jù)之外,還需要有一個(gè)路由跳轉(zhuǎn)的功能。這個(gè)功能是通過(guò)redux-router-redux的push方法提供的。也是通過(guò)smart組件Home.js通過(guò)屬性傳給Preview的。有了push方法,Preview組件就可以綁定點(diǎn)擊事件,實(shí)現(xiàn)路由跳轉(zhuǎn)到詳情頁(yè)了。

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) {
        //阻止原生鏈接跳轉(zhuǎn)
        e.preventDefault();
        //使用react-router-redux的方法進(jìn)行跳轉(zhuǎn),方便更新store
        // 遇到一個(gè)錯(cuò)誤,原來(lái)是跳轉(zhuǎn)地址沒(méi)有寫對(duì)
        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目錄

這個(gè)目錄下就放的是通過(guò)connect包裝的smart組件以及他們控制的dumb組件集成起來(lái)的reducer和action(*Redux.js)
HomeRedux里引入了dumb組件需要的reducer和actions。
Home組件存放的是用connect包裝后的smart組件。接受的state為home.list組件樹(shù),同時(shí)也dispatch兩個(gè)方法:listAction(用于dispatch獲得文章列表的action)和push(用于路由轉(zhuǎn)換,由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, //取出整個(gè)Redux狀態(tài)樹(shù)中 home.list分支作為當(dāng)前組件的Props
    }
}, dispatch => {
    return {
        listActions: bindActionCreators(actions, dispatch),
        push:bindActionCreators(push,dispatch)
    }
})(Home);

views 下面還包括了一個(gè)簡(jiǎn)單的Detail組件,用于展示點(diǎn)擊的文章詳情,非常簡(jiǎn)陋。

import React, {Component} from 'react';

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

export default Detail;

Route 目錄

這個(gè)目錄下放整個(gè)項(xiàng)目的路由框架。此時(shí)我們的路由其實(shí)非常簡(jiǎn)單,就是一個(gè)博文列表主頁(yè),和每一個(gè)博文的詳細(xì)頁(yè)面。這兩個(gè)部分公用layouts,所以就用一個(gè)Frame把這兩個(gè)部分包裹起來(lái)

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集成成一個(gè)state,目前的狀態(tài)樹(shù)只有來(lái)自HomeRedux這一個(gè)枝。

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

export default {
    home,
};
DevTools.js

配置了需要的redux開(kāi)發(fā)工具。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環(huán)境變量process.env.NODE_ENV判斷生產(chǎn)環(huán)境和開(kāi)發(fā)環(huán)境,加載不同的配置文件,差別就是是否隱藏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中間件,使用中間件擴(kuò)充了store,也集成了需要的reducer。最后創(chuàng)建了最終的store。


最后總結(jié)

跟著《深入react技術(shù)棧》的第五章搭出來(lái)的一個(gè)簡(jiǎn)單的react+redux以及一些它們的插件們的前端應(yīng)用,使用mock數(shù)據(jù)做偽后臺(tái)。之前遇到了因?yàn)槭褂玫膎pm包版本問(wèn)題導(dǎo)致的各種跑不動(dòng)的問(wèn)題,之后還是都解決了。深深的感受到了被版本支配的恐懼。之后要做的就是完善這個(gè)系統(tǒng),做成一個(gè)完整的有CRUD功能的blog,最好能夠加進(jìn)MongoDB數(shù)據(jù)庫(kù),而不是用mock提供數(shù)據(jù)交互。還有頁(yè)面可以美化一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容