文件目錄

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è)面可以美化一下。