搭建一個(gè)react app項(xiàng)目

前言

使用react-router、webpack、redux一步步啟動(dòng)一個(gè)react app項(xiàng)目
項(xiàng)目github源碼:https://github.com/chenshaomei/react-douban

初始化項(xiàng)目

1、首先創(chuàng)建一個(gè)空文件夾 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、運(yùn)行命令下載依賴模塊:

$ npm install

4、在react-douban工程內(nèi),創(chuàng)建以下目錄:

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'
  },
   //啟動(dòng)的webpack server配置
  devServer: {
    contentBase: path.join(__dirname, "build"),  //以build為根目錄提供文件
    historyApiFallback: true,
    hot: true,
    port:8092, //端口號(hào)
    inline: true,
    // api 代理轉(zhuǎn)發(fā) 
    proxy:{
        "/api": {
            target: "http://api.douban.com/v2/",
            pathRewrite: {"^/api" : ""},
            changeOrigin: true
        }
    }
  },
  devtool: 'source-map',
  //引入模塊時(shí)就不需要寫后綴了,會(huì)自動(dòng)補(bǔ)全
  resolve: {
      modules: [
          path.join(__dirname, "app"),
          "node_modules"
      ],
     extensions: ['.js', '.json', '.css', '.scss']
  },
  // 加載器,對(duì)模塊的處理邏輯
  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: [
    // 自動(dòng)生成 html
    new HtmlwebpackPlugin({
      template: __dirname + "/index.html" // 指向自己創(chuàng)建的index.html的位置
  }),
  new webpack.HotModuleReplacementPlugin()
  ]
};

react-router

1、在main.js文件中添加以下內(nèi)容(入口文件)

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文件中添加以下內(nèi)容(路由文件)

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文件中添加以下內(nèi)容(根組件)

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中添加以下內(nèi)容
在Index組件下,渲染Home(首頁) 和My(我的)頁面 ,并且渲染兩個(gè)頁面有公共的導(dǎo)航組件(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、創(chuàng)建導(dǎo)航組件components/Nav/Nav.js

  • components/Nav/Nav.js中添加以下內(nèi)容
    activeIndex 是導(dǎo)航選中的下標(biāo),設(shè)置顯示高亮
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中添加以下內(nèi)容
.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:描述“發(fā)生了什么”
它是 store 數(shù)據(jù)的唯一來源。一般來說你會(huì)通過 store.dispatch() 將 action 傳到 store

2、reducer

reducer:根據(jù) action 更新 state

  • 創(chuàng)建reducer
    reducers/index.js文件中添加如下內(nèi)容
import { combineReducers } from 'redux';
import home from './home'
//使用redux的combineReducers方法將所有reducer打包合并起來
const rootReducer = combineReducers({
  home
})
export default rootReducer

拆分 Reducer,一個(gè)reducer只負(fù)責(zé)一個(gè)頁面的state;(例如home只負(fù)責(zé)管理首頁的state更新),然后再使用reduxcombineReducers方法將所有reducer打包合并起來

3、store

Store 就是把actionreducer聯(lián)系到一起的對(duì)象,Redux 應(yīng)用只有一個(gè)單一的 store

  • 注冊(cè)store
    stores/index.js文件中添加如下內(nèi)容
// 注冊(cè)store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import { createLogger } from 'redux-logger' // 利用redux-logger打印日志
import reducer from '../reducers'

// 調(diào)用日志打印方法
const loggerMiddleware = createLogger()
//applyMiddleware來自redux可以包裝 store 的 dispatch
//thunk作用是使action創(chuàng)建函數(shù)可以返回一個(gè)function代替一個(gè)action對(duì)象
const createStoreWithMiddleware = applyMiddleware(
  thunk,
  loggerMiddleware
)(createStore)
export default function configureStore(initialState) {
  const store = createStoreWithMiddleware(reducer, initialState)
  //熱替換選項(xiàng)
  if (module.hot) {
    // Enable Webpack hot module replacement for reducers
    module.hot.accept('../reducers', () => {
      const nextReducer = require('../reducers')

      store.replaceReducer(nextReducer)
    })
  }
  return store
}
最后編輯于
?著作權(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)容