搭建一个react app项目

前言

使用react-router、webpack、redux一步步启动一个react app项目
项目github源码:https://github.com/chenshaomei/react-douban

初始化项目

1、首先创建一个空文件夹 react-douban,初始化package.json

$ npm init

2、package.json文件添加依赖

{
  "name": "react-douban",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "browser-sync start --server --files **/*.css, **/*.html, **/*.js",
    "dev": "webpack-dev-server"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.25.0",
    "babel-loader": "^7.1.1",
    "babel-preset-es2015": "^6.24.1",
    "babel-preset-react": "^6.24.1",
    "browser-sync": "^2.18.12",
    "css-loader": "^0.28.4",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "react-hot-loader": "^1.3.1",
    "react-router": "^4.1.1",
    "style-loader": "^0.18.2"
  },
  "dependencies": {
    "file-loader": "^0.11.2",
    "html-loader": "^0.4.5",
    "html-webpack-plugin": "^2.29.0",
    "isomorphic-fetch": "^2.2.1",
    "query-string": "^4.3.4",
    "react-redux": "^5.0.5",
    "react-router": "^2.8.1",
    "redux": "^3.7.2",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.2.0",
    "url-loader": "^0.5.9",
    "webpack": "^3.2.0",
    "webpack-dev-server": "^2.5.1",
    "whatwg-fetch": "^2.0.3"
  }
}

3、运行命令下载依赖模块:

$ npm install

4、在react-douban工程内,创建以下目录:

image.png

5、在 index.html文件中添加以下代码

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no,minimal-ui">
        <title>react-project</title>
    </head>
    <body>
        <div id="app"></div> 
    </body>
</html>

6、在 .babelrc文件中添加以下代码

{
    presets: [['es2015'], ['react']]
}

配置webpack

1、全局安装

$ npm install webpack -g

2、配置webpack文件,webpack.config.js

var path = require('path');
var webpack = require('webpack');
var HtmlwebpackPlugin = require('html-webpack-plugin');


module.exports = {
    // 打包的入口文件
  entry: __dirname + '/app/main.js',
   // 打包输出文件夹以及文件名
  output: {
    path: __dirname+'/build' ,
    publicPath: '/',
    filename: 'bundle.js'
  },
   //启动的webpack server配置
  devServer: {
    contentBase: path.join(__dirname, "build"),  //以build为根目录提供文件
    historyApiFallback: true,
    hot: true,
    port:8092, //端口号
    inline: true,
    // api 代理转发 
    proxy:{
        "/api": {
            target: "http://api.douban.com/v2/",
            pathRewrite: {"^/api" : ""},
            changeOrigin: true
        }
    }
  },
  devtool: 'source-map',
  //引入模块时就不需要写后缀了,会自动补全
  resolve: {
      modules: [
          path.join(__dirname, "app"),
          "node_modules"
      ],
     extensions: ['.js', '.json', '.css', '.scss']
  },
  // 加载器,对模块的处理逻辑
  module: {
    loaders: [
       {test:/\.css$/, loader: 'style-loader!css-loader'},
       {
           test: /\.html?$/,
           loader: 'html-loader',
       },
       {test:/\.(png|jpe?g|gif|svg)(\?.*)?$/, loader: 'url-loader?limit=8192'},
       {test:/\.js$/, loader: 'react-hot-loader!babel-loader', exclude: /node_modules/},
    ]
  },
  //插件
  plugins: [
    // 自动生成 html
    new HtmlwebpackPlugin({
      template: __dirname + "/index.html" // 指向自己创建的index.html的位置
  }),
  new webpack.HotModuleReplacementPlugin()
  ]
};

react-router

1、在main.js文件中添加以下内容(入口文件)

iimport React from 'react';
import { render } from 'react-dom';
import { Router, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import configureStore from './stores';

// 引入store
const store = configureStore();

// 引入路由
import { routes } from './router';

// 引入全局css
import './css/base.css';

render(
    <Provider store = { store }>
    <Router history={browserHistory}>
        {
            routes
        }
    </Router>
    </Provider>
    , document.getElementById('app'));

2、在router.js文件中添加以下内容(路由文件)

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

// 引入pages组件
import App from './App';
import Index from './pages/Index';
import Home from './pages/Home';
import Login from './pages/Login';
import Details from './pages/Details';

// 定义路由
const routes = (
    <Route path="/" component={ App }>
        <IndexRoute component={ Index }/>
        <Route path="/" component={ Index }>
            <Route path="/home" component={ Home } />
            <Route path="/my" component={ Login } />
        </Route>
        <Route path="/details/:id" component={ Details } />
    </Route>
)

export { routes };

3、在App.js文件中添加以下内容(根组件)

import React from 'react';
import ReactDom from 'react-dom';

import Nav from './components/Nav/Nav';

export default React.createClass({
  render() {
    return <div>
      {this.props.children}
    </div>
  }
})

4、在pages/Index.js中添加以下内容
在Index组件下,渲染Home(首页) 和My(我的)页面 ,并且渲染两个页面有公共的导航组件(components/Nav/Nav

import React from 'react';
import ReactDom from 'react-dom';

import Nav from '../components/Nav/Nav';
import Home from './Home';
export default class Index extends React.Component{
    constructor(props){
        super(props);
        // nav data
        this.state = {
            navList: [
                {
                    path:'/home',
                    icon:require('../images/icons/home.png'),
                    txt:'首页'
                },
                {
                    path:'/my',
                    icon:require('../images/icons/my.png'),
                    txt:'我的'
                }
            ]
        }
    }
    render(){
        return <div>
            {this.props.children || <Home />}
            <Nav navList = { this.state.navList }/>
        </div>
    }
}

5、创建导航组件components/Nav/Nav.js

  • components/Nav/Nav.js中添加以下内容
    activeIndex 是导航选中的下标,设置显示高亮
import React from 'react';
import { Link } from 'react-router';

import './Nav.css';
export default class Nav extends React.Component{
    constructor(props){
        super(props);
    }

    render(){
        let activeIndex = 0;
        if(location.pathname !='/'){
            activeIndex = -1;
        }
        // nav data
        let navList = this.props.navList || [];

        return <div className="nav">

            <ul className="nav-ul">
            {
                navList.map((item,index)=>{

                    return  <li key={index}  ><Link to={item.path} className={index==activeIndex?'active':''} activeClassName="active"><i></i><span className="txt">{item.txt}</span></Link></li>
                })
            }
            </ul>
        </div>
    }
}
  • components/Nav/Nav.css中添加以下内容
.nav{
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: 49px;
}
.nav::before{
    content: '';
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    height: 1px;
    background: #ccc;
    -webkit-transform-origin:0 0;
    transform-origin:0 0;
    -webkit-transform:scaleY(0.5);
    transform:scaleY(0.5);
}
.nav-ul{
    display: -webkit-box; /* OLD - iOS 6-, Safari 3.1-6 */
    display: -webkit-flex; /* NEW - Chrome */
    display: flex;
    width: 100%;
    height: 100%;
    background: #fff;

    -webkit-box-pack: center;
    -webkit-box-align: center;
    justify-content: center;
    align-items: center
}
.nav-ul li{
    width: 0%;
    -webkit-box-flex: 1;
    -webkit-flex: 1;
    flex: 1;
}
.nav-ul li a{
    display: block;
    text-align: center;
}

.nav-ul li a span{
    display: block;
    color: #999;
    font-size: 12px;
}
.nav-ul li a i{
    display: block;
    width: 24px;
    height: 24px;
    margin: 0 auto;
}
.nav-ul li:nth-child(1) a i{
    background: url(../../images/icons/home.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li:nth-child(2) a i{
    background: url(../../images/icons/search.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li:nth-child(3) a i{
    background: url(../../images/icons/my.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li:nth-child(1) a.active i{
    background: url(../../images/icons/home-active.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li:nth-child(2) a.active i{
    background: url(../../images/icons/search-active.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li:nth-child(3) a.active i{
    background: url(../../images/icons/my-active.png) no-repeat top center;
    background-size: 100%;
}
.nav-ul li a.active span{
    color: #333;
}

redux

1、action

action:描述“发生了什么”
它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store

2、reducer

reducer:根据 action 更新 state

  • 创建reducer
    reducers/index.js文件中添加如下内容
import { combineReducers } from 'redux';
import home from './home'
//使用redux的combineReducers方法将所有reducer打包合并起来
const rootReducer = combineReducers({
  home
})
export default rootReducer

拆分 Reducer,一个reducer只负责一个页面的state;(例如home只负责管理首页的state更新),然后再使用reduxcombineReducers方法将所有reducer打包合并起来

3、store

Store 就是把actionreducer联系到一起的对象,Redux 应用只有一个单一的 store

  • 注册store
    stores/index.js文件中添加如下内容
// 注册store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger' // 利用redux-logger打印日志
import reducer from '../reducers'

// 调用日志打印方法
const loggerMiddleware = createLogger()
//applyMiddleware来自redux可以包装 store 的 dispatch
//thunk作用是使action创建函数可以返回一个function代替一个action对象
const createStoreWithMiddleware = applyMiddleware(
  thunk,
  loggerMiddleware
)(createStore)
export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(reducer, initialState)
  //热替换选项
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers')

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

推荐阅读更多精彩内容