文件目录
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,分别放着
import React, {Component} from 'react';
import Nav from './Nav';
class Frame extends Component {
render() {
return (
{this.props.children} {/* 渲染的默认是Home*/}
)
}
}
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 (
)
}
}
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 Oops, something is wrong
}
if (loading) {
return Loading
}
return (
{articleList.map(item => (
))}
)
}
}
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 (
{this.props.title}
{this.props.date}
{this.props.description}
)
}
}
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 (
Home1
)
}
}
// 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 (
Detail
)
}
}
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 => (
);
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(
);
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提供数据交互。还有页面可以美化一下。