[React] react+redux+router+webpack+antd環境搭建一版

很久以前搭建的一個react執行環境,受歷史影響是webpack3.10.0和webpack-dev-server2.7.1的環境,新項目準備用webpack4從新弄弄了,舊的記錄就合併發佈了(在沒有快速目錄的地方寫的,以前是分爲了10個文件)css

所使用的一些包可能進過不斷的迭代升級已不支持html

01.初始化項目(安裝須要的包)

//生成package.json
npm init

安裝基礎包

npm install react react-dom --save
npm install webpack@3.10.0 webpack-dev-server@2.7.1 --save-dev

<!--webpack和webpack-dev-server同時全局安裝一下-->
npm install webpack@3.10.0 wepack-dev-server@2.7.1 -g

webpack-dev-server@2.7.3是最後兼容ie9的版本,升級以後會致使打包的文件在ie9下運行報錯node

安裝和配置babel

npm install 
babel-core 核心功能
babel-loader 
babel-polyfill  轉換低版本代碼
babel-preset-env 解析Es6
babel-preset-react 解析JSX
--save-dev

安裝loader包

npm install 
css-loader 
style-loader 
file-loader
url-loader 
image-webpack-loader
html-loader
--save-dev
  • --save 會把依賴包名稱添加到 package.json 文件 dependencies

dependencies是運行時依賴,是咱們發佈後還須要依賴的模塊react

  • --save-dev 則添加到 package.json 文件 devDependencies

devDependencies是開發時的依賴,在發佈後用不到它,而只是在咱們開發纔用到它jquery

當npm install時,會下載dependencies和devDependencies中的模塊,當使用npm install –production或者註明NODE_ENV變量值爲production時,只會下載dependencies中的模塊webpack

02.執行程序,建立文件

建立/.babelrc

//.babelrc
{
  "presets": [
    "react",
    "env"
  ]
}

建立webpack.config.js

const path = require('path');

module.exports = { 
    context: path.resolve(__dirname, 'app'),
    resolve: {
        extensions: ['.js', '.jsx'], //後綴名自動補全,可使用jsx的文件後綴
        modules: [
            path.resolve(__dirname, 'node_modules')
        ]
    },
    entry: './app.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'app.js',
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['env', 'react'],
                    }
                }
            },
            {
                test: /index\.html/,
                use: [{
                    loader: 'file-loader',
                    options: {
                        name (file) {
                            return 'index.[ext]'
                        }
                    }
                }]
            }
        ]
    },
};

建立/app/component/App.js

import React from 'react';
import Header from './Header';
import Content from './Content';
import Sidebar from './Sidebar';

class App extends React.Component {
    render() {
        return (
            <div className="app">
                <Header/>
                <Sidebar/>
                <Content/>
            </div>
        )
    }
}
export default App;

建立/app/component/Header.js

import React from 'react';

class Header extends React.Component {
    render() {
        return (
            <div>this is header</div>
        )
    }
}
export default Header;

建立/app/component/Sidebar.js

import React from 'react';

class Sidebar extends React.Component {
    render() {
        return (
            <div className="sidebar">
                this is sidebar
            </div>
        )
    }
}
export default Sidebar;

建立/app/component/Content.js

import React from 'react';

class Content extends React.Component {
    render() {
        return (
            <div className="content">
                this is content
            </div>
        )
    }
}
export default Content;

建立/app/app.js入口文件

import './index.html';
import React from 'react';
import ReactDom from 'react-dom';
import App from './component/App';

ReactDom.render(
    <App/>, document.getElementById('root')
);

建立/app/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <base href="/">
    <meta charset="UTF-8">
    <title>hello world</title>
</head>
<body>
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>

執行webpack打包

<!--控制檯執行webpack會在/dist中生成一個app.js和index.html-->
webpack

開啓本地服務器

修改webpack.config.js

//webpack-dev-server配置
devServer: {
    contentBase: './dist',
    historyApiFallback: true,
    inline: true,//源文件改變,會自動刷新頁面
    port: 1234,//設置默認監聽端口,若是省略,默認爲"8080"
},

修改package.json

"scripts": {
    "start": "webpack-dev-server"
  },

執行start

npm start

image

03.添加css樣式

新建/style/app.css

html, body, #root, .app {
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
}

div {
    box-sizing: border-box;
}

.header {
    position: fixed;
    top: 0;
    left: 150px;
    right: 0;
    padding: 0 30px;
    height: 61px;
    background: #ebebeb;

}

.title {
    width: 90%;
    margin: 0 5%;
    height: 80px;
    line-height: 80px;
    color: #fff;
    font-size: 20px;
    text-align: center;
    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}

.sidebar {
    position: fixed;
    top: 0;
    bottom: 0;
    left: 0;
    width: 150px;
    background: #3d3c4a;

}

.header-con {
    position: relative;
    width: 100%;
    height: 60px;
}

.user {
    position: absolute;
    right: 0;
    top: 15px;
    height: 30px;
    line-height: 30px;
    color: gray;
}

.text {
    float: left;
    height: 30px;
}

.avatar {
    float: right;
    width: 30px;
    height: 30px;

}

.avatar img {
    width: 100%;
    height: 100%;
    border-radius: 90%;
}

.content {
    position: fixed;
    top: 60px;
    left: 150px;
    right: 0;
    bottom: 0;
    padding: 30px;
    color: gray;
}

修改/app/component/Header.js

import React from 'react';

class Header extends React.Component {
    render() {
        return (
            <div className="header">
               <div className="header-con">
                   <div className="user">
                       歡迎您!
                   </div>
               </div>
            </div>
        )
    }
}
export default Header;

修改/app/component/Sidebar.js

import React from 'react';

class Sidebar extends React.Component {
    render() {
        return (
            <div className="sidebar">
                <div className="title">
                    easterCat
                </div>
            </div>
        )
    }
}
export default Sidebar;

修改/app/component/App.js

...
import '../style/app.css';

添加css依賴/webpack.config.js

module: {
        rules: [
            {
                test: /\.css$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader'
                }],
            },
        ]
    },

打包運行

webpack

此時的css打包是自動添加到index的style標籤裏面git

04.添加圖片的依賴

建立/app/images/avatar.jpg

修改/app/component/Header.js

import React from 'react';
import Avatar from '../images/avatar.jpg';

class Header extends React.Component {
    render() {
        return (
            <div className="header">
                <div className="header-con">
                    <div className="user">
                        <span className="text">歡迎您!</span>
                        <span className="avatar">
                            <img src={Avatar} alt="" />
                        </span>
                    </div>
                </div>
            </div>
        )
    }
}
export default Header;

添加圖片依賴/webpack.config.js

module: {
        rules: [
            {
                test: /.*\.(gif|png|jpe?g|svg)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: 'images/[name].[ext]'
                    }
                }, {
                    loader: 'image-webpack-loader'
                }]
            },
        ]
    },

05.添加預編譯語言-sass

<!--less和sass都用,此處用sass-->
npm install sass-loader node-sass --save-dev

修改webpack.config.js

module: {
    rules:[{
        test: /\.scss$/,
        use: [{
            loader: 'style-loader'
        }, {
            loader: 'css-loader',
            options: {
                sourceMap: true
            }
        }, {
            loader: 'sass-loader',
            options: {
                sourceMap: true,
                outputStyle: 'expanded',
                sourceMapContents: true
            }
        }]
    },]
},

添加app.scss

@import 'body';
@import 'header';
@import 'sidebar';
@import 'content';

將app.css拆分紅body.scss/header.scss/sidebar.scss/content.scss

<!--例:header.scss-->
.header {
    position: fixed;
    top: 0;
    left: 150px;
    right: 0;
    padding: 0 30px;
    height: 61px;
    background: #ebebeb;
    .user {
        position: absolute;
        right: 0;
        top: 15px;
        height: 30px;
        line-height: 30px;
        color: gray;
    }
    .header-con {
        position: relative;
        width: 100%;
        height: 60px;
    }
    .text {
        float: left;
        height: 30px;
    }

    .avatar {
        float: right;
        width: 30px;
        height: 30px;
        img {
            width: 100%;
            height: 100%;
            border-radius: 90%;
        }
    }
}

修改/app/component/app.js

import '../style/app.css';

<!--修改成-->
import '../style/app.scss';

從新打包

webpack

安裝第二個預編譯語言less

npm install less less-loader --save-dev

修改webpack.config.js

module: {
    rules: [
        {
            test: /\.less$/,
            use: [{
                loader: 'style-loader'
            }, {
                loader: 'css-loader',
                options: {
                    sourceMap: true
                }
            }, {
                loader: 'less-loader',
                options: {
                    sourceMap: true,
                    outputStyle: 'expanded',
                    sourceMapContents: true,
                }
            }]
        },
    ]
},

06.添加一個ui框架antd-design

antd-designgithub

npm install antd --save

<!--babel-plugin-import必需要裝-->
npm install babel-plugin-import --save-dev

引入antd樣式

<!--在App.js中引入-->
import 'antd/dist/antd.css' 或 import 'antd/dist/antd.less'

<!--在app.scss中引入-->
@import "~antd/dist/antd.css";

使用組件,修改content.js

import React from 'react';
import Table from 'antd/lib/Table';

class Content extends React.Component {
    render() {
        const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        }, {
            title: '年齡',
            dataIndex: 'age',
            key: 'age',
        }, {
            title: '電話號碼',
            dataIndex: 'number',
            key: 'number',
        }, {
            title: '郵箱',
            dataIndex: 'email',
            key: 'email',
        }];


        const data = [];
        for (let i = 1; i < 15; i++) {
            let obj = {
                name: 'doudou',
                age: 32,
                number: 123456789,
                email: '123456789@163.com',
            };
            obj.key = i;
            data.push(obj);
        }

        return (
            <div className="content">
                <Table columns={columns} dataSource={data}/>
            </div>
        )
    }
}
export default Content;

babel-plugin-import插件

從 antd 引入模塊便可,無需引入樣式,babel-plugin-import 會加載 JS 和 CSSweb

修改.babelrc

//.babelrc
{
  "plugins": [
    [
      "import",
      {
        "libraryName": "antd",
        "style": true //引入less,若是使用css文件就改成'css'
      }
    ]
  ]
}

修改/app/component/Content.js

import {Table} from 'antd';

此時app.scss和App.js中引入的antd.css能夠去掉了npm

問題

若是在編譯antd的樣式文件報錯,更換一個less,當前是@2.7.2

使用antd-design

更改/app/component/App.js

import React from 'react';
import Header from './layout/Header';
import Content from './layout/Content';
import Sidebar from './layout/Sidebar';
import '../style/app.scss';
import {Layout} from 'antd';

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            collapsed: false
        };

        this.toggle = () => {
            this.setState({
                collapsed: !this.state.collapsed
            })
        };
    }

    render() {
        return (
            <Layout className="layout-app">
                <Layout.Sider
                    trigger={null}
                    collapsible
                    collapsed={this.state.collapsed}
                >
                    <Sidebar/>
                </Layout.Sider>
                <Layout>
                    <Layout.Header style={{background: '#fff', padding: 0}}>
                        <Header collapsed={this.state.collapsed}
                                toggle={this.toggle}
                        />
                    </Layout.Header>
                    <Layout.Content style={{margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280}}>
                        <Content/>
                    </Layout.Content>
                </Layout>
            </Layout>
        )
    }
}
export default App;

修改/app/component/layout/Header.js

import React from 'react';
import avatar_img from '../../images/avatar.jpg';
import {Icon, Avatar} from 'antd';


class Header extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        const {
            collapsed,
            toggle
        } = this.props;

        return (
            <div className="layout-header">
                <Icon
                    className="trigger"
                    type={collapsed ? 'menu-unfold' : 'menu-fold'}
                    onClick={toggle}
                />
                <Avatar className="avatar"
                        src={avatar_img}/>
            </div>
        )
    }
}
export default Header;

修改/app/component/layout/Sidebar.js

import React from 'react';
import {Menu, Icon} from 'antd';

class Sidebar extends React.Component {
    constructor(props) {
        super(props);
    }

    render() {
        return (
            <div className="layout-sidebar">
                <div className="logo"/>
                <Menu theme="dark" mode="inline" defaultSelectedKeys={['1']}>
                    <Menu.Item key="1">
                        <Icon type="user"/>
                        <span>nav 1</span>
                    </Menu.Item>
                    <Menu.Item key="2">
                        <Icon type="video-camera"/>
                        <span>nav 2</span>
                    </Menu.Item>
                    <Menu.Item key="3">
                        <Icon type="upload"/>
                        <span>nav 3</span>
                    </Menu.Item>
                </Menu>
            </div>
        )
    }
}
export default Sidebar;

修改/app/component/layout/Content.js

import React from 'react';
import {Table} from 'antd';

class Content extends React.Component {
    render() {
        const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        }, {
            title: '年齡',
            dataIndex: 'age',
            key: 'age',
        }, {
            title: '電話號碼',
            dataIndex: 'number',
            key: 'number',
        }, {
            title: '郵箱',
            dataIndex: 'email',
            key: 'email',
        }];

        const data = [];
        for (let i = 1; i < 15; i++) {
            let obj = {
                name: 'doudou',
                age: 32,
                number: 123456789,
                email: '123456789@163.com',
            };
            obj.key = i;
            data.push(obj);
        }

        return (
            <Table columns={columns} dataSource={data}/>
        )
    }
}
export default Content;

新建layout.scss

.layout-app {
    height: 100%;
}

.layout-sidebar {
    .logo {
        height: 32px;
        background: #333;
        border-radius: 6px;
        margin: 16px;
    }
}

.layout-header {
    .trigger {
        font-size: 18px;
        line-height: 64px;
        padding: 0 16px;
        cursor: pointer;
        transition: color .3s;
        &:hover {
            color: #108ee9;
        }
    }
    .avatar {
        float: right;
        margin-top: 16px;
        margin-right: 16px;
        cursor: pointer;
    }
}

07.引入路由react-router

安裝react-router

npm install react-router-dom --save

修改/app/app.js

import './index.html';
import './styles/app.less';
import React from 'react';
import ReactDom from 'react-dom';
import App from './components/App.jsx';
import {BrowserRouter, Route} from 'react-router-dom';

ReactDom.render(
    <BrowserRouter>
        <Route path="/" component={App}/>
    </BrowserRouter>
    ,
    document.getElementById('root')
);

修改/app/component/App.js

import React from 'react';
import Home from './Home';
import Login from './Login';
import {Route} from 'react-router-dom';

class App extends React.Component {
    componentDidMount() {
        const {
            location,
            history
        } = this.props;
        if (location.pathname === '/home' || location.pathname === '/') {
            history.replace('/home')
        } else if (location.pathname === '/login') {
            history.replace('/login')
        }
    }

    render() {
        return (
            <div className="app">
                <Route path="/home" component={Home}/>
                <Route path="/login" component={Login}/>
            </div>
        )
    }
}

export default App;

添加/app/component/home/Home.js

import React from 'react';
import {matchPath} from 'react-router-dom';
import Header from './Header';
import Content from './Content';
import Sidebar from './Sidebar';
import {Layout} from 'antd';

export default class Home extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            collapsed: false
        };
        this.ress = ['content01', 'content02', 'content03'];
        this.res = null;
        const match = matchPath(this.props.history.location.pathname, {
            path: '/home/:res'
        });
        if (match) {
            this.res = match.params.res;
        }

        this.toggle = () => {
            this.setState({
                collapsed: !this.state.collapsed
            })
        };
    }

    componentWillMount() {
        if (!this.res || !this.res.length || this.ress.indexOf(this.res) === -1) {
            this.props.history.replace(`/home/content01`)
        }
    }

    render() {
        return (
            <Layout className="layout-app">
                <Layout.Sider
                    trigger={null}
                    collapsible
                    collapsed={this.state.collapsed}
                >
                    <Sidebar res= {this.res}/>
                </Layout.Sider>
                <Layout>
                    <Layout.Header style={{background: '#fff', padding: 0}}>
                        <Header collapsed={this.state.collapsed}
                                toggle={this.toggle}
                        />
                    </Layout.Header>
                    <Layout.Content style={{margin: '24px 16px', padding: 24, background: '#fff', minHeight: 280}}>
                        <Content/>
                    </Layout.Content>
                </Layout>
            </Layout>
        )
    }
}

添加/app/component/login/Login.js

import React from 'react';
import {Form, Icon, Input, Button, Checkbox} from 'antd';
const FormItem = Form.Item;

class Logining extends React.Component {
    constructor(props) {
        super(props);

        this.handleSubmit = (e) => {
            const {
                history
            } = this.props;

            e.preventDefault();
            this.props.form.validateFields((err, values) => {
                if (!err) {
                    if (values.userName === 'admin' && values.password === '123456') {
                        history.replace('/home')
                    }
                    console.log('Received values of form: ', values);
                }
            });
        }
    }

    render() {
        const {
            getFieldDecorator
        } = this.props.form;

        return (
            <div className="login">
                <Form onSubmit={this.handleSubmit} className="login-form">
                    <FormItem>
                        {getFieldDecorator('userName', {
                            rules: [{required: true, message: 'Please input your username!'}],
                        })(
                            <Input prefix={<Icon type="user" style={{fontSize: 13}}/>} placeholder="Username"/>
                        )}
                    </FormItem>
                    <FormItem>
                        {getFieldDecorator('password', {
                            rules: [{required: true, message: 'Please input your Password!'}],
                        })(
                            <Input prefix={<Icon type="lock" style={{fontSize: 13}}/>} type="password"
                                   placeholder="Password"/>
                        )}
                    </FormItem>
                    <FormItem>
                        {getFieldDecorator('remember', {
                            valuePropName: 'checked',
                            initialValue: true,
                        })(
                            <Checkbox>Remember me</Checkbox>
                        )}
                        <a className="login-form-forgot" href="">Forgot password</a>
                        <Button type="primary" htmlType="submit" className="login-form-button">
                            Log in
                        </Button>
                        Or <a href="">register now!</a>
                    </FormItem>
                </Form>
            </div>
        );
    }
}

const Login = Form.create()(Logining);

export default Login;

修改/app/component/layout/Sidebar.js

import React from 'react';
import {Menu, Icon} from 'antd';
import {NavLink} from 'react-router-dom';

export default class Sidebar extends React.Component {
    render() {
        const {
            res
        } = this.props;
        return (
            <div className="layout-sidebar">
                <div className="logo"/>
                <Menu theme="dark"
                      mode="inline"
                      defaultSelectedKeys={[res]}
                >
                    <Menu.Item key="content01">
                        <NavLink to="/home/content01">
                            <Icon type="user"/>
                            <span>nav 1</span>
                        </NavLink>
                    </Menu.Item>
                    <Menu.Item key="content02">
                        <NavLink to="/home/content02">
                            <Icon type="video-camera"/>
                            <span>nav 2</span>
                        </NavLink>
                    </Menu.Item>
                    <Menu.Item key="content03">
                        <NavLink to="/home/content03">
                            <Icon type="upload"/>
                            <span>nav 3</span>
                        </NavLink>
                    </Menu.Item>
                </Menu>
            </div>
        )
    }
}

修改/app/component/layout/Content.js

import React from 'react';
import {Route} from 'react-router-dom';
import Content01 from './content01';
import Content02 from './content02';
import Content03 from './content03';

export default class Content extends React.Component {
    render() {
        return (
            <div>
                <Route path="/home/Content01" component={Content01}/>
                <Route path="/home/Content02" component={Content02}/>
                <Route path="/home/Content03" component={Content03}/>
            </div>
        )
    }
}

修改/app/index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <base href="/">
    <meta charset="UTF-8">
    <title>hello world</title>
</head>
<body>
<div id="root"></div>
<script src="./app.js"></script>
</body>
</html>

08.引入狀態管理react-redux

阮一峯redux

阮一峯react-redux

安裝redux

npm i redux react-redux --save

新建/app/store/store.js

import {createStore} from 'redux';
import user from '../components/content01.reducer';

const store = createStore(user);

export default store;

修改/app/app.js

import {Provider} from 'react-redux';
import store from './store/store';

ReactDom.render(
    <Provider store={store}>
        <BrowserRouter>
            <Route path="/" component={App}/>
        </BrowserRouter>
    </Provider>
    ,
    document.getElementById('root')
);

新建/app/component/content01.reducer.js

let initState = {
    data: [{
        name: 'doudou',
        age: 32,
        phone: 123456789,
        email: '123456789@163.com',
        key: 1,
    }]
};

function user(state = initState, action) {
    const data = state.data;
    switch (action.type) {
        case 'ADD_ONE_USER':
            data[data.length] = action.payload;
            return {data: data};
        default:
            return state;
    }
}
export default user;

新建/app/component/content01.action.js

export function addOneUser(value) {
    return {
        type: 'ADD_ONE_USER',
        payload: value
    }
}

修改/app/component/content01.jsx

import React from 'react';
import {Table, Icon, Button} from 'antd';
import {connect} from 'react-redux';
import {addOneUser} from './content01.action';
import FromContent from './FromContent';

class Content01 extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            visible: false,
            date: null
        };
        this.showModal = () => {
            this.setState({
                visible: true,
                data: new Date()
            });
        };
        this.closeModal = () => {
            this.setState({
                visible: false,
            });
        };

        this.submit = (values) => {
            values.key = Date.parse(new Date());
            this.props.addOneUser(values);
            this.closeModal();
        }
    }

    render() {
        const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
        }, {
            title: '年齡',
            dataIndex: 'age',
            key: 'age',
        }, {
            title: '電話號碼',
            dataIndex: 'phone',
            key: 'phone',
        }, {
            title: '郵箱',
            dataIndex: 'email',
            key: 'email',
        }];

        const data = this.props.data;

        return (
            <div>
                <Button type="primary" onClick={this.showModal}>
                    <Icon type="user-add"/>添加
                </Button>
                <Table columns={columns} dataSource={data}/>
                {
                    this.state.visible ? <FromContent date={this.state.date}
                                                      submit={this.submit}
                                                      showModal={this.showModal}
                                                      closeModal={this.closeModal}
                    /> : null
                }
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        data: state.data
    }
};

const mapDispatchToProps = (dispatch) => {
    return {
        addOneUser: (value) => dispatch(addOneUser(value))
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(Content01);

新建/app/component/FromContent.js

import React from 'react';
import {Modal, Button, Form, Input} from 'antd';
const FormItem = Form.Item;
import {increaseAction} from './content01.action';

class FromContent01 extends React.Component {
    constructor(props) {
        super(props);

        this.handleSubmit = (e) => {
            e.preventDefault();
            this.props.form.validateFields((err, values) => {
                if (!err) {
                    console.log('Received values of form: ', values);
                    this.props.submit(values);
                }
            });
        }
    }

    render() {
        const {getFieldDecorator} = this.props.form;
        const {closeModal, date} = this.props;

        return (
            <Modal
                title="添加一個成員"
                key={date}
                visible={true}
                onCancel={closeModal}
                footer={null}
            >
                <Form onSubmit={this.handleSubmit} className="login-form">
                    <FormItem label="姓名">
                        {getFieldDecorator('name', {
                            rules: [{required: true, message: 'Please input your name!'}],
                        })(
                            <Input placeholder="姓名"/>
                        )}
                    </FormItem>
                    <FormItem label="年齡">
                        {getFieldDecorator('age', {
                            rules: [{required: true, message: 'Please input your age!'}],
                        })(
                            <Input placeholder="年齡"/>
                        )}
                    </FormItem>
                    <FormItem label="電話號碼">
                        {getFieldDecorator('phone', {
                            rules: [{
                                required: true, message: 'Please input your phone!',
                            }],
                        })(
                            <Input type="電話號碼"/>
                        )}
                    </FormItem>
                    <FormItem label="郵箱">
                        {getFieldDecorator('email', {
                            rules: [{
                                required: true, message: 'Please input your email!',
                            }],
                        })(
                            <Input type="郵箱"/>
                        )}
                    </FormItem>
                    <FormItem>
                        <Button type="primary" htmlType="submit" className="login-form-button">
                            Log in
                        </Button>
                    </FormItem>
                </Form>
            </Modal>
        )
    }
}
const FromContent = Form.create()(FromContent01);
export default FromContent;

09.更好的使用redux

redux-freeze運行時報錯機制

redux-thunkredux的異步中間件

redux-logger日誌中間件

redux-immutable在不可變數據類型中合併reducer

redux-create-reducer使用action和reducer的關聯變的簡單

關於redux中間件的講解

安裝依賴插件

npm i --save redux-immutable redux-freeze redux-thunk redux-logger immutable redux-create-reducer

修改/app/store/store.js

import {createStore, applyMiddleware, compose} from 'redux';
import {combineReducers} from 'redux-immutable';
import freeze from "redux-freeze"
import thunk from 'redux-thunk'
import logger from 'redux-logger'
import {Map} from 'immutable';
import reducers from './reducers';

let middlewares = [];
middlewares.push(thunk);
middlewares.push(logger);
middlewares.push(freeze);

//添加中間件
let middleware = applyMiddleware(...middlewares);
//添加redux dev tools,能夠在谷歌商城裏直接安裝工具,搜索名字
middleware = compose(middleware, window.devToolsExtension());

const reducer = combineReducers(reducers);

const store = createStore(
    reducer,
    Map({}),
    middleware
);

export default store;

新建/app/store/reducers.js

import user from '../components/content01.reducer';
export default {
    user
}

修改/app/component/content01.reducer.js

import {createReducer} from 'redux-create-reducer';
import {fromJS} from 'immutable';

import {
    ADD_ONE_USER
} from './content01.actions';

const initState = fromJS({
    data: [{
        name: 'doudou',
        age: 32,
        phone: 123456789,
        email: '123456789@163.com',
        key: 1,
    }]
});

const handlers = {
    [ADD_ONE_USER]: (user, action) => {
        return user.set('data', user.get('data').push(fromJS(action.payload)));
    }
};

export default createReducer(initState, handlers);

修改/app/component/content01.action.js

export const ADD_ONE_USER = 'ADD_ONE_USER';

export function addOneUser(value) {
    return dispatch => {
        return dispatch({
            type: 'ADD_ONE_USER',
            payload: value
        })
    }
}

修改/app/component/content01.js

const mapStateToProps = (state) => {
    return {
        data: state.get('user').get('data'),
    }
};

const mapActionCreators = {
    addOneUser
};

export default connect(mapStateToProps, mapActionCreators)(Content01);

10.優化環境

下載須要的依賴

npm i 
extract-text-webpack-plugin
copy-webpack-plugin
clean-webpack-plugin
webpack-merge
html-webpack-plugin
postcss-smart-import
postcss-loader
precss
--save-dev

將webpack.config.js拆分

webpack.common.js
const path = require('path');
const ROOTPATH = path.resolve(__dirname, '.');

module.exports = {
    context: path.resolve(ROOTPATH, 'app'),
    resolve: {
        extensions: ['.js', '.jsx'], //後綴名自動補全,可使用jsx的文件後綴
        modules: [path.resolve(ROOTPATH, "node_modules")],
        alias: {
            COMPONENTS_PATH: './components',
        }
    },
    entry: {
        app: ['babel-polyfill', './app.js']
    },
    module: {
        rules: [
            {
                test: /\.(js|jsx)$/,
                exclude: /(node_modules|bower_components)/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['env', 'react'],
                    }
                }
            },
            {
                test: /.*\.(gif|png|jpe?g|svg)$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 10000,
                        name: 'images/[name].[ext]'
                    }
                }, {
                    loader: 'image-webpack-loader'
                }]
            },
            {
                test: /\.html$/,
                use: [{
                    loader: 'html-loader',
                    options: {
                        minimize: false
                    }
                }],
            },
            {
                test: /\.(woff|svg|eot|ttf)\??.*$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 50000,
                    }
                }]
            }
        ]
    },
    devServer: {
        contentBase: path.resolve(ROOTPATH, './app'),//爲一個目錄下的文件提供本地服務器,在這裏設置其所在目錄
        historyApiFallback: true,//跳轉將指向index.html
        inline: true,//開啓自動刷新頁面
        port: 1234,//設置默認監聽端口,若是省略,默認爲"8080"
        hot: true,//開啓熱替換
    },
    plugins: [],
};

webpack.dev.js

const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');
const path = require('path');

const ROOTPATH = path.resolve(__dirname, '.');

module.exports = merge(common, {
    output: {
        path: path.resolve(ROOTPATH, 'dist'), //打包的文件夾
        filename: '[name].js',
        publicPath: ''
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader'
                }],
            },
            {
                test: /\.scss$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader',
                    options: {
                        sourceMap: true
                    }
                }, {
                    loader: 'sass-loader',
                    options: {
                        sourceMap: true,
                        outputStyle: 'expanded',
                        sourceMapContents: true
                    }
                }]
            },
            {
                test: /\.less$/,
                use: [{
                    loader: 'style-loader'
                }, {
                    loader: 'css-loader',
                    options: {
                        sourceMap: true
                    }
                }, {
                    loader: 'less-loader',
                    options: {
                        sourceMap: true,
                        outputStyle: 'expanded',
                        sourceMapContents: true,
                    }
                }]
            },
            {
                test: /index\.html/,
                use: [{
                    loader: 'file-loader',
                    options: {
                        name: 'index.[ext]'
                    }
                }]
            }
        ]
    },
    plugins: [],
});

webpack.pro.js

const path = require('path');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const UglifyJsPlugin = webpack.optimize.UglifyJsPlugin;
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;

const ROOTPATH = path.resolve(__dirname, '.');

module.exports = merge(common, {
    output: {
        path: path.resolve(ROOTPATH, 'dist'), //打包的文件夾
        filename: '[name].[hash].bundle.js',
        publicPath: ''
    },
    module: {
        rules: [
            {
                test: /\.css$/,
                use: ExtractTextPlugin.extract({fallback: 'style-loader', use: ['css-loader']}),
            },
            {
                test: /\.scss$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: 'css-loader'
                    }, {
                        loader: 'sass-loader'
                    }]
                })
            },
            {
                test: /\.less$/,
                use: ExtractTextPlugin.extract({
                    use: [{
                        loader: "css-loader", options: {importLoaders: 1}
                    }, {
                        loader: "postcss-loader"
                    }, {
                        loader: 'less-loader'
                    }]
                })
            },
        ]
    },
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                NODE_ENV: JSON.stringify('production')
            }
        }),
        new ExtractTextPlugin({
            filename: '[name].[contenthash].css',
            allChunks: true, // don't contain embedded styles
        }),
        //加入js壓縮的實例
        new UglifyJsPlugin({
            mangle: {
                mangle: false
            },
            compress: {
                sequences: true,
                dead_code: true,
                conditionals: true,
                booleans: true,
                unused: false,
                if_return: true,
                join_vars: true,
                drop_console: false,
                warnings: false
            },
        }),
        new HtmlWebpackPlugin({
            title: 'xxx局',
            filename: path.resolve(ROOTPATH, 'dist/template.html'), // the path to create html
            template: path.resolve(ROOTPATH, 'app/public/template.html'), //the path of template html,
            minify: false,
            // favicon: path.resolve(__dirname, 'app/images/cscec_favicon-2.ico')
        }),
        new CleanWebpackPlugin(['dist', 'dist.zip', 'dist.rar'], {exclude: ['lib']}),
        new CommonsChunkPlugin({
            name: 'vendor',
            filename: 'vendor.bundle.js',
            minChunks: function (module, count) {
                // any required modules inside node_modules are extracted to vendor
                return (
                    module.resource &&
                    /\.js$/.test(module.resource) &&
                    module.resource.indexOf(
                        path.join(__dirname, './node_modules')
                    ) === 0
                )
            }
        }),
    ],
});

建立app/public/template.html

修改package.json

"scripts": {
    "build": "set NODE_ENV=production&&webpack -p --config webpack.pro.js",
    "start": "webpack-dev-server --devtool eval --progress --colors --config webpack.dev.js "
  },

建立postcss.config.js

module.exports = {
    plugins: [
        require('postcss-smart-import')({ /* ...options */ }),
        require('precss')({ /* ...options */ }),
        require('autoprefixer')({ /* ...options */ })
    ]
};

執行npm run build

生成的代碼已經進過了分離,壓縮,添加了版本號,可以利用緩存提高加載速度

11.在react中使用jsplumb、jquery和jquery-ui。

方法1.ProvidePlugin

自動加載模塊,不用再每一個組件使用require和import

<!--在webpack.common.js的plugins中添加-->
new webpack.ProvidePlugin({
    "$": "jquery",
    "jQuery": "jquery",
    "window.jQuery": "jquery"
})

此時可使用全局變量$和jQuery了,能夠在任意一個組件中console.log($)就能夠看到一個函數

<!--引入須要jquery的插件-->
import jp from '../../plugins/jsplumb/jsplumb';
import "jquery-ui/ui/widgets/draggable";

接下來就可使用jquery和jquery的插件了

<!--可使用$以及使用jquery-ui的draggable方法了-->
    $(el).draggable({
        helper: "clone",
        containment: ".js-layout",//設置拖動的範圍
        scroll: false,
        cursor: "pointer",//設置拖動光標老是在中心
        cursorAt: {top: 30, left: 60},
        start: function (ev) {
            wh = {
                w: $(this).width() / 2,
                h: $(this).height() / 2
            }
        },
        drag: function (ev) {
            offset = $(this).offset();

        },
        stop: function (ev) {
            console.log('鼠標的位置', {top: ev.clientY, left: ev.clientX});
            console.log('拖放元素的位置', {top: offset.top, left: offset.left});
            let pos = {x: ev.clientX - offset.left - wh.w - 9, y: ev.clientY - offset.top - wh.h - 100 - 8};
            movecb(pos)
        }
    });

webpack文檔

方法2.expose-loader

使用expose-loader能夠暴露全局變量

module: {
        rules: [
            {
                test: require.resolve('jquery'),
                use: [{
                    loader: 'expose-loader',
                    options: 'jQuery'
                }, {
                    loader: 'expose-loader',
                    options: '$'
                }]
            },
        ]
    },

此時的效果和方法一的同樣 webpack文檔

使用jquery-ui的方法

使用npm
<!--能夠經過npm i下載-->
npm i jquery-ui --save

<!--而後在組件中引入-->
import "jquery-ui/ui/widgets/draggable";

須要一個個引入你須要的方法,也能夠作一個總文件進行引入

直接下載

從官網下載jquery-ui,而後放到項目中的一個文件夾下

import "../../plugins/jquery-ui"

這樣能夠直接引入生效,可是打包的文件會大很多

查看文章

webpack多頁應用架構系列(四):老式jQuery插件還不能丟,怎麼兼容? Webpack引入jquery及其插件的幾種方法

12.開發經常使用工具

  • prop-types
  • classnames

附1.依賴

搭建框架

babel 官網 中文網站 api文檔

webpack

webpack-dev-server

babel基本

babel其餘

react

react

react-router

react-router

[react-router-dom]

redux

redux

react-redux

redux-freeze運行時報錯機制

redux-thunkredux的異步中間件

redux-logger日誌中間件

redux-immutable在不可變數據類型中合併reducer

redux-create-reducer使用action和reducer的關聯變的簡單

關於redux中間件的講解

ant-design

在react.js上使用antd-design

antd-design

babel-plugin-import 用於antd按需加載

loader

less-loader

css-loader

style-loader

babel-polyfill

[file-loader]

[url-loader]

[image-webpack-loader]

html-webpack-plugin

[extract-text-webpack-plugin] 將css單獨打包

相關文章
相關標籤/搜索