新手級配置 react react-router4.0 redux fetch sass


前言

最近公司可能要用react,然而有幾個同事歷來沒用過react,而後領導讓我帶他們學習react, 爲下一個react項目作基礎。
而後隨手寫了幾個demo,幫助他們瞭解一下如何去構建配置項目。
如今分享出來,但願能夠幫助到須要的人。 本demo 中有些目錄雖然沒有用,可是我仍是列了出來,目的是爲了展現一個正規項目的目錄骨架結構。
create-react-app 模板文件我也沒有歸類,等了解以後,能夠本身歸類,加一個樣式的文件夾。css

正文

就目前的大環境而言,在開發react或vue項目的時候,應該沒有幾我的還直接在html直接飲用react的庫文件吧,都是用的前端構建工具webpack + es6 來寫項目。因此直接就推薦facebook官方出品的腳手架create-react-app來作項目。 create-react-app是最適合新手來學習使用的腳手架,所有是簡單的命令。不須要像別的腳手架同樣,先去clone整個項目,而後再安裝依賴。html

第一步 (安裝工具,生成項目, 啓動項目)

安裝create-react-app工具前端

npm install -g create-react-app

生成項目vue

create-react-app react-demo

生成項目以後自動會下載依賴所有,
進入項目以後,啓動項目node

cd react-demo
npm start 或者 yarn start

npm 和 yarn 都是包管理器, 均可如下載依賴,具體這裏不作解釋,詳情能夠本身搜索。一個項目最好用一種包管理工具,不要混用,下面都用yarn來下載依賴。不作另外說明react

項目啓動以後打開3000端口。
項目啓動成功以下圖:webpack


第二步 (配置react-router4.0,包含路由傳參等)

一、安裝依賴git

yarn add react-router-dom --save

二、新建文件、配置路由
src 目錄下 新建 containers 目錄 裏面放置主頁面文件
src 目錄下 新建 components 目錄 裏面放置高複用模板文件
containers 中新建 Home.jses6

Home.js 代碼github

import React, { Component } from 'react';
class Home extends Component {
    render() {
        return (
            <div>
                Home
            </div>
        );
    }
}

export default Home;

containers 中新建 List.js

List.js 代碼

import React, { Component } from 'react';
class List extends Component {
    render() {
        return (
            <div>
                List
            </div>
        );
    }
}

export default List;

containers 中新建 Mine.js

Mine.js 代碼 (二級路由,路由傳參示例)

import React, {Component} from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom';
import Mine1 from './Mine1'
import Mine2 from './Mine2'

class Mine extends Component {
    constructor(props) {
        super(props);
        this.state = {
            param: ''
        };
    }
    changeParam(event) {
        this.setState({
            param: event.target.value
        })
    }
    render() {
        return (
            <Router>
                <div>
                    Mine
                    傳參示例:<input type="text" placeholder='請輸入要傳遞到mine2的參數' value={this.state.param} onChange={this.changeParam.bind(this)}/>
                    <div className="mine-nav">
                        {/*編寫導航*/}
                        <ul>
                            <li><Link to={`${this.props.match.url}/mine1`}>Mine1</Link></li>
                            <li><Link to={`${this.props.match.url}/mine2/${this.state.param? this.state.param : "default-param"}`}>Mine2</Link></li>
                            {/*傳參示例,若是沒有參數,傳默認參數*/}
                        </ul>
                        {/*路由匹配*/}
                        <div>
                            <Route exact path={`${this.props.match.url}/mine1`} component={Mine1}/>
                            <Route path={`${this.props.match.url}/mine2/:param`} component={Mine2}/>
                        </div>
                    </div>
                </div>
            </Router>
        );
    }
}

export default Mine;

containers 中新建 Mine1.js

Mine1.js 代碼

import React, { Component } from 'react';
class Mine1 extends Component {
    render() {
        return (
            <div>
                Mine1
            </div>
        );
    }
}

export default Mine1;

containers 中新建 Mine2.js

Mine2.js 代碼 (路由傳參示例)

import React, { Component } from 'react';
class Mine2 extends Component {
    render() {
        return (
            <div>
                2222222222222 傳遞的參數:{this.props.match.params.param}
            </div>
        );
    }
}

export default Mine2;

修改入口App.js 文件,修改成以下所示:

import React, { Component } from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom';
import List from './containers/List.js'
import Home from './containers/Home.js'
import Mine from './containers/Mine.js'
import './App.css'
import logo from './logo.svg'

class App extends Component {
    render() {
        return (
            <div className="App">
                <div className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <h2 className='App-title'>Welcome to React Plan</h2>
                </div>
                <div className="App-content">
                    {/*路由配置*/}
                    <Router>
                        <div className="content-box">
                            {/*編寫導航*/}
                            <ul className="nav">
                                <li><Link to="/">首頁</Link></li>
                                <li><Link to="/list">列表頁</Link></li>
                                <li><Link to="/mine/mine1">個人頁面二級路由</Link></li>
                                {/*link指向二級路由的默認頁面*/}
                            </ul>
                            {/*路由匹配*/}
                            <div className="content">
                                <Route exact path="/" component={Home}/>
                                <Route path="/list" component={List}/>
                                <Route path="/mine" component={Mine}/>
                            </div>
                        </div>
                    </Router>
                </div>
            </div>
        );
    }
}

export default App

修改App.css 文件,讓稍微有一點樣式:

.App {
    text-align: center;
}
.App-logo {
    animation: App-logo-spin infinite 20s linear;
    height: 80px;
}
.App-header {
    background-color: #222;
    height: 150px;
    padding: 20px;
    color: white;
}
.App-title {
    font-size: 1.5em;
}
.App-intro {
    font-size: large;
}
@keyframes App-logo-spin {
    from {
        transform: rotate(0deg);
    }
    to {
        transform: rotate(360deg);
    }
}

/*下面是新添加的樣式*/
* {
    margin: 0;
    padding: 0;
    list-style: none;
}
.content-box{
    overflow: hidden;
}
.nav {
    padding: 20px;
    float: left;
    width: 160px;
    background-color: #fcc;
}
.nav li{
    line-height: 30px;
}
.content{
    padding: 20px 0 0 200px;
    background-color: #ccf;
    height: 110px;
}
.mine-nav{
    background-color: #cfc;
    margin-top: 20px;
    height: 70px;
}
.mine-nav ul{
    overflow: hidden;
}
.mine-nav ul li{
    float: left;
    width: 50%;
}
.mine-nav>div{
    margin-top: 20px;
}

大功告成,點一點試試吧。配置完畢,頁面以下圖:

第三步 (配置redux)

redux具體是什麼,這裏就很少提,想詳細瞭解最好看一下阮一峯老師的博客 Redux 入門教程 來了解什麼是redux, 或者你的項目到底需不須要用redux。這裏只講如何去配置redux.
一、安裝依賴

yarn add redux react-redux --save

二、新建文件, 配置redux
src 目錄下 新建 store 目錄裏面放置 store配置文件
src 目錄下 新建 reducers 目錄裏面放置 規則性文件
src 目錄下 新建 constants 目錄裏面放置 數據
src 目錄下 新建 actions 目錄裏面放置 觸發更新文件
store 中新建 configStore.js

store > configStore.js 文件

import { createStore } from 'redux'
//引入規則文件
import rootReducer from '../reducers/index.js'
export default function configStore(initState){
        // 建立store
        const store = createStore(
            rootReducer,
            initState,
            // 若是安裝了redux插件可按照版本打開下面的註釋
            // window.devToolsExtension ? window.devToolsExtension() : undefined
            // window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : undefined
        )
        return store
}

reducers 中新建 index.js
reducers 中新建 number.js

reducers > index.js 文件

// 規則性文件
import { combineReducers } from 'redux'
import number from './number'
// 若是有多個規則,則引入多個文件
//import XXXX from './XXXX'

// 建立規則
export default combineReducers({
    // XXXX,
    number
})

reducers > number.js 文件

import * as actionTypes from '../constants/number.js'
import { combineReducers } from 'redux'

const text =  function (state = actionTypes.TEXT, action){
    switch(action.type) {
        case actionTypes.TEXT :
            return action.data.text
        default:
            return state
    }
}

const count =  function (state = actionTypes.COUNT, action){
    switch(action.type) {
        case actionTypes.COUNT :
            return action.data.count
        default:
            return state
    }
}

export default combineReducers({
    text,
    count
})

constants 中新建 number.js

constants > number.js 文件

export const COUNT = 100;
export const TEXT = 'number大類下的數據';

修改 containers 中的 Home.js (稍微把home組件改爲使用redux的形式)

containers > Home.js 文件

import React, { Component } from 'react';
import {connect} from 'react-redux';
class Home extends Component {
    render() {
        return (
            <div>
                Home <br/>
                {this.props.text} ==> {this.props.count}
            </div>
        );
    }
}

// 若是隻是讀取數據,那麼只須要這一個方法便可。(這裏兩個寫出來只是爲了方便理解)
function mapStateToProps(state){
    return {
        count: state.number.count,
        text: state.number.text
    }
}
// 同理,若是隻是設置數據,那麼只須要這一個方法便可。
function mapDispatchToProps(dispatch) {
    return {

    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(Home)

修改 src 中的 index.js (給之前看的本博客的小夥伴們說聲道歉,漏了這一步,如今補上。這一步是給store增長入口)

src > index.js 文件

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import {Provider} from 'react-redux'
import configStore from './store/configStore.js'

const store = configStore();

ReactDOM.render(
    <Provider store={store}>
        <App/>
    </Provider>,
    document.getElementById('root'));
registerServiceWorker();

作到這裏,應該已經實現了第一步,能夠讀取數據了。 如圖:

讀取數據實現了,接下來確定要實現設置數據。

containers 中的新建 TestRedux.js

containers > TestRedux.js 文件

import React, { Component } from 'react';
import {bindActionCreators} from 'redux'
import {connect} from 'react-redux'
import * as handlerNumberActions from '../actions/number.js'

class TestRedux extends Component {
    textChange(event){
        this.props.handlerNumberActions.changeText({
            text: event.target.value
        })
    }
    numberAdd(){
        this.props.handlerNumberActions.addNumber({
            count: this.props.count
        })
    }
    numberSubtract(){
        this.props.handlerNumberActions.subtractNumber({
            count: this.props.count
        })
    }
    
    
    render() {
        return (
            <div style={{height:'100px',background: '#ffc',padding: '10px'}}>
                <ul>
                    <li>修改redux數據</li>
                    <li>修改文本: <input type="text" style={{padding: '5px 10px'}} value={this.props.text} onChange={this.textChange.bind(this)} /></li>
                    <li>操做數值:
                        <button style={{padding: '5px 10px'}} onClick={this.numberAdd.bind(this)}>+</button>
                        -------------------
                        <button style={{padding: '5px 10px'}} onClick={this.numberSubtract.bind(this)}>-</button>
                    </li>
                </ul>
            </div>
        );
    }
}

function mapStateToProps(state){
    return {
        count: state.number.count,
        text: state.number.text
    }
}
function mapDispatchToProps(dispatch) {
    return {
        handlerNumberActions: bindActionCreators(handlerNumberActions, dispatch)
    }
}
export default connect(
    mapStateToProps,
    mapDispatchToProps
)(TestRedux)

actions 中的新建 number.js

actions > number.js 文件

import * as actionTypes from '../constants/number.js'

export function addNumber(data){
    data.count ++;
    return {
        type: actionTypes.COUNT,
        data
    }
}
export function subtractNumber(data){
    data.count --;
    return {
        type: actionTypes.COUNT,
        data
    }
}

export function changeText(data){
    return {
        type: actionTypes.TEXT,
        data
    }
}

修改 src 中的 App.js (把剛寫好的TestRedux組件添加進去)

src > App.js 文件

import React, { Component } from 'react';
import {BrowserRouter as Router, Route, Link} from 'react-router-dom';
import List from './containers/List.js'
import Home from './containers/Home.js'
import Mine from './containers/Mine.js'
import TestRedux from "./containers/TestRedux"
import './App.css'
import logo from './logo.svg'

class App extends Component {
    render() {
        return (
            <div className="App">
                <div className="App-header">
                    <img src={logo} className="App-logo" alt="logo" />
                    <h2 className='App-title'>Welcome to React Plan</h2>
                </div>
                <div className="App-content">
                    {/*路由配置*/}
                    <Router>
                        <div className="content-box">
                            {/*編寫導航*/}
                            <ul className="nav">
                                <li><Link to="/">首頁</Link></li>
                                <li><Link to="/list">列表頁</Link></li>
                                <li><Link to="/mine/mine1">個人頁面二級路由</Link></li>
                                {/*link指向二級路由的默認頁面*/}
                            </ul>
                            {/*路由匹配*/}
                            <div className="content">
                                <Route exact path="/" component={Home}/>
                                <Route path="/list" component={List}/>
                                <Route path="/mine" component={Mine}/>
                            </div>
                        </div>
                    </Router>
                </div>
                <TestRedux />
            </div>
        );
    }
}

export default App

至此,完整的redux就完全的配置完畢了。點一點,或者修改一下文本試試吧。
目錄結構和效果以下圖:


第四步 (配置fetch)

fetch是什麼呢?fetch實際上是 XMLHttpRequest 的替代品,比起 XMLHttpRequest 這種粗糙的東西,fetch顯然看起來精緻多了,最棒的是fetch 是基於 Promise 的,讓咱們擺脫了地獄回調的噩夢。下面咱們就把fetch 簡單封裝一下。方便在react中使用。
一、安裝依賴

yarn add whatwg-fetch es6-promise --save

二、新建文件 封裝 fetch 方法

src 目錄下的新建 fetch 目錄, 在 fetch 文件下新建 index.js 封裝 fetch

fetch > index.js 文件

import 'whatwg-fetch'
import 'es6-promise'

export function get(url) {
    let result = fetch(url, {
        credentials: 'include',
        headers: {
            'Access-Control-Allow-Origin': '*',
            'Accept': 'application/json, text/plain, */*'
        },
        // 設置容許cors跨域
        mode: 'cors'
    });
    return result;
}

// 將對象拼接成 key1=val1&key2=val2&key3=val3 的字符串形式
function obj2params(obj) {
    let result = '';
    let item;
    for (item in obj) {
        result += '&' + item + '=' + encodeURIComponent(obj[item]);
    }
    
    if (result) {
        result = result.slice(1);
    }
    
    return result;
}

// 發送 post 請求
export function post(url, paramsObj) {
    let result = fetch(url, {
        method: 'POST',
        credentials: 'include',
        headers: {
            'Accept': 'application/json, text/plain, */*',
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: obj2params(paramsObj)
    });
    
    return result;
}

public 目錄下 新建 mock 目錄 放置 模擬數據。
mock 目錄下 新建 list.json 文件。

public > mock > list.json

{
    "errorNo": 0,
    "message": "success",
    "data": [
        {
            "name": "Kelli",
            "age": 12
        },
        {
            "name": "Vivien",
            "age": 17
        },
        {
            "name": "Jacklyn",
            "age": 19
        },
        {
            "name": "Bobbie",
            "age": 32
        }
    ]
}

修改 src > containers 下的 List.js 文件。

src > containers > List.js

import React, {Component} from 'react';
import {get} from '../fetch/index';

class List extends Component {
    constructor(props) {
        super(props);
        this.state = {}
    }
    componentWillMount() {
        this.getListData()
    }
    getListData() {
        get("./mock/list.json").then((res) => {
            return res.json();
        }).then((json)=>{
            this.setState({
                dataList: json.data
            })
        }).catch(function (err) {
            console.log(err);
        })
    }
    
    render() {
        let _this = this;
        function createListDom() {
            return {
                __html: _this.state.dataList && _this.state.dataList.map( item => {
                    return '<li>name: '+ item.name + ',age: '+ item.age +'</li>'
                }).join('')
            };
        }
        return (
            <div>
                List <br/>
                <ul dangerouslySetInnerHTML={createListDom()} />
            </div>
        );
    }
}

export default List;

這是隻是簡單的測試了get 方法,至於post 請求,有興趣的你們能夠私下去測試。
配置完成以下圖:

第五步 (配置sass)

在高版本的create-react-app中去掉了支持sass的功能,若是咱們還想用只能本身配置,須要改動配置文件。
一、安裝依賴

yarn add node-sass sass-loader --save-dev

二、修改配置

node_modules/react-scripts/config 下找到 webpack.config.dev.js 文件,
先在 exclude 中按照規律在後面添加 /.scss$/ ,
而後再在loaders中配置 sass文件規則。

{
    test: /\.scss$/,
    loaders: ['style-loader', 'css-loader', 'sass-loader']
}

改兩個地方的配置以後,sass就配置好了,如圖:

webpack.config.dev.js 是開發環境的配置文件,若是想在生產環境也生效,那麼在 webpack.config.prod.js 作一樣配置的修改。

到這裏,全部的配置就所有完成了。項目親測能夠運行。

做者 HoChine
2018 年 04月 27日
項目演示: http://hochine.cn/demo/react-demo
GitHub地址: https://github.com/HoChine/react-demo

相關文章
相關標籤/搜索