zoukankan      html  css  js  c++  java
  • webpack/react/redux/react-router/ts一步步搭建架子

    mkdir stage && cd stage // 创建项目文件夹进入项目
    npm init // 初始化依赖
    npm install -S react react-dom  // 安装react相关依赖
    npm install -D webpack webpack-cli webpack-dev-server // 安装webpack相关依赖
    npm install -D html-webpack-plugin clean-webpack-plugin // 安装生成html和清理html文件的插件
    npm install -D babel-loader @babel/core @babel/preset-env @babel/preset-react // 安装babel-loader解析react
    npm install -D less style-loader css-loader less-loader // 安装less依赖及相关的开发loader mkdir src config build
    // 根目录下创建src、config、build文件夹 touch babel.config.js // 根目录下创建babel.config.js cd build && touch webpack.config.js // build目录下创建webpack.config.js
    cd ../src && touch index.js && touch index.less && touch index.html // src目录下创建index.js、index.less和index.html
    // babel.config.js
    module.exports = {
        presets: [
            "@babel/preset-env",
            "@babel/preset-react",
        ],
    }
    // build/webpack.config.js
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
        entry: './src/index.js',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist'),
        },
        devServer: {
            port: 3001,
        },
        devtool: 'inline-source-map',
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
            })
        ],
        module: {
            rules: [
                {
                    test: /.js$/,
                    exclude: /node_modules/,
                    loader: "babel-loader",
                },
                {
                    test: /.less$/,
                    exclude: /node_modules/,
                    use: [
                        'style-loader',
                        { loader: 'css-loader', options: { modules: true } },
                        'less-loader',
                    ]
                },
            ]
        }
    };
    // src/index.js
    import React from 'react'
    import { render } from 'react-dom'
    import styles from './index.less'
    const App = () => ( <div className={styles.hohoho}>STAGE HOHOHO</div> ) render(<App />, document.getElementById('root'))
    // src/index.less
    .hohoho {
        color: #008000;
    }
    <!-- src/index.html -->
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>STAGE</title>
    </head>
    
    <body>
        <div id="root"></div>
    </body>
    
    </html>

    修改package.json,添加执行脚本

    "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "start": "webpack-dev-server --config ./build/webpack.config.js --open",
        "build": "webpack --config ./build/webpack.config.js"
      },

    此时执行npm run build可以看到build目录下生成了dist文件夹 ,npm start可以启动3001端口访问到写的index.js中的内容(如果有报错请检查依赖是否安装成功)

     ###### ###### ###### ###### ###### ###### ###### ###### ###### ###### ######

    接入react-router

    npm install -D react-router-dom // 安装react-router-dom依赖

    修改src/index.js文件,此处使用的是HashRouter,如果使用BrowserRouter需要服务端做相应的响应,原理可以对比hash路由和history的区别(可以分别使用两种Router,切换路由时看具体网络请求就明白了)

    // src/index.js
    import React from 'react'
    import { render } from 'react-dom'
    import {
        HashRouter,
        Route,
        Switch,
        Redirect,
    } from 'react-router-dom'
    import styles from './index.less'
    
    const Home = () => (
      
    <div>HOME HOHOHO</div>
    ) const Page1 = () => (   <div>PAGE1 HOHOHO</div>
    ) const Page2 = () => (     <div>PAGE2 HOHOHO</div>
    ) const App = () => ( <> <div className={styles.hohoho}>STAGE HOHOHO</div> <li><a href='#/home'>去home</a></li> <li><a href='#/page1'>去page1</a></li> <li><a href='#/page2'>去page2</a></li> <hr /> <HashRouter> <Switch> <Route exact path='/home' component={Home} /> <Route exact path='/page1' component={Page1} /> <Route exact path='/page2' component={Page2} />
              <Redirect from='/' to='/home' /> </Switch> </HashRouter> </> ) render(<App />, document.getElementById('root'))

    此时可以来回切换home、page1、page2三个页面

     ###### ###### ###### ###### ###### ###### ###### ###### ###### ###### ######

    接入redux 

    npm install -S redux react-redux // 安装redux相关依赖
    mkdir models && cd models && mkdir stores actions reducers // 在src目录下创建redux相关的文件夹,并分别在目录下创建index.js
    cd stores && touch index.js && cd ../actions && touch index.js && cd ../reducers && touch index.js // 分别创建index.js文件
    // src/models/actions/index.js
    export const CREATE_TODO = 'CREATE'; // 增加一个todo
    export const DELETE_TODO = 'DELETE'; // 删除一个todo
    export const CREATE_TYPE = 'CREATE_TYPE'; // 添加操作
    export const DELETE_TYPE = 'DELETE_TYPE'; // 删除操作
    // src/models/reducers/index.js
    import {
        CREATE_TODO,
        DELETE_TODO,
        CREATE_TYPE,
        DELETE_TYPE,
    } from '../actions'
    
    export function todos(state = [], action) {
        switch (action.type) {
            case CREATE_TODO: {
                return [...state, { id: action.id, text: action.text, completed: false }]
            }
            case DELETE_TODO: {
                return [...state].filter(({ id }) => id !== action.id)
            }
            default: {
                return state;
            }
        }
    }
    
    export function operateCounter(state = { createCounter: 0, deleteCounter: 0 }, action) {
        const { createCounter, deleteCounter } = state;
        switch (action.type) {
            case CREATE_TYPE: {
                return { ...state, createCounter: createCounter + 1 }
            }
            case DELETE_TYPE: {
                return { ...state, deleteCounter: deleteCounter + 1 }
            }
            default: {
                return state;
            }
        }
    }
    // src/models/stores/index.js
    import { combineReducers, createStore } from 'redux'
    import * as reducers from '../reducers'
    
    const todoApp = combineReducers(reducers)
    export default createStore(todoApp)

    修改src/index.js,里面的HOME,PAGE1,PAGE2组件应该分别抽离在不同的页面中

    // src/index.js
    import React from 'react'
    import { render } from 'react-dom'
    import {
        HashRouter,
        Route,
        Switch,
        Redirect,
    } from 'react-router-dom'
    import { Provider, connect } from 'react-redux'
    import store from './models/stores'
    import {
        CREATE_TODO,
        DELETE_TODO,
        CREATE_TYPE,
        DELETE_TYPE,
    } from './models/actions'
    import styles from './index.less'
    
    const HomeOld = (props) => {
        const {
            todos = [],
            operateCounter: {
                createCounter = 0,
                deleteCounter = 0,
            },
        } = props;
        return (
            <>
                <div>HOME HOHOHO</div>
                <div>当前todos如下,可以在page1与page2中操作todos列表:</div>
                <div className={styles.hohoho}>添加操作: {createCounter} 次,删除操作: {deleteCounter} 次</div>
                {todos.map(({ text, id }) => (<li key={id}>{`id:${id}-text:${text}`}</li>))}
            </>
        )
    }
    const mapStateToPropsHome = state => {
        return {
            todos: state.todos,
            operateCounter: state.operateCounter,
        };
    };
    const Home = connect(mapStateToPropsHome)(HomeOld);
    
    
    
    
    
    const Page1Old = (props) => {
        const { todos = [], dispatch } = props;
        let input;
        function onClick() {
            const { id = 0 } = [...todos].pop() || {};
            dispatch({
                type: CREATE_TODO,
                id: id + 1,
                text: input.value,
            });
            dispatch({ type: CREATE_TYPE });
        }
        return (
            <>
                <div>PAGE1 HOHOHO</div>
                <input ref={node => { input = node }} />
                &nbsp;&nbsp;
                <button onClick={onClick}>添加</button>
                {todos.map(({ text, id }) => (<li key={id}>{`id:${id}-text:${text}`}</li>))}
            </>
        )
    }
    const mapStateToPropsPage1 = state => {
        return {
            todos: state.todos,
        };
    };
    const Page1 = connect(mapStateToPropsPage1)(Page1Old);
    
    
    
    
    
    const Page2Old = (props) => {
        const { todos = [], dispatch } = props;
        function onClick(id) {
            dispatch({
                type: DELETE_TODO,
                id,
            });
            dispatch({ type: DELETE_TYPE });
        }
        return (
            <>
                <div>PAGE2 HOHOHO</div>
                {todos.map(({ text, id }) => (
                    <li key={id}>
                        {`id:${id}-text:${text}`}
                        &nbsp;&nbsp;
                        <a href="javascript:;" onClick={onClick.bind(null, id)}>删除该项</a>
                    </li>
                ))}
            </>
        )
    }
    const mapStateToPropsPage2 = state => {
        return {
            todos: state.todos,
        };
    };
    const Page2 = connect(mapStateToPropsPage2)(Page2Old);
    
    
    
    
    
    
    const App = () => (
        <Provider store={store}>
            <div className={styles.hohoho}>STAGE HOHOHO</div>
            <li><a href='#/home'>去home</a></li>
            <li><a href='#/page1'>去page1</a></li>
            <li><a href='#/page2'>去page2</a></li>
            <hr />
            <HashRouter>
                <Switch>
                    <Route exact path='/home' component={Home} />
                    <Route exact path='/page1' component={Page1} />
                    <Route exact path='/page2' component={Page2} />
                    <Redirect from='/' to='/home' />
                </Switch>
            </HashRouter>
        </Provider>
    )
    
    render(<App />, document.getElementById('root'))

     接入react-router和react-redux完成,可以看到todolist,此处贴上完整的package.json

    {
      "name": "stage",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1",
        "start": "webpack-dev-server --config ./build/webpack.config.js --open",
        "build": "webpack --config ./build/webpack.config.js"
      },
      "author": "",
      "license": "ISC",
      "dependencies": {
        "react": "^16.13.1",
        "react-dom": "^16.13.1",
        "react-redux": "^7.2.0",
        "redux": "^4.0.5"
      },
      "devDependencies": {
        "@babel/core": "^7.10.4",
        "@babel/preset-env": "^7.10.4",
        "@babel/preset-react": "^7.10.4",
        "babel-loader": "^8.1.0",
        "clean-webpack-plugin": "^3.0.0",
        "css-loader": "^3.6.0",
        "html-webpack-plugin": "^4.3.0",
        "less": "^3.11.3",
        "less-loader": "^6.2.0",
        "react-router-dom": "^5.2.0",
        "style-loader": "^1.2.1",
        "webpack": "^4.43.0",
        "webpack-cli": "^3.3.12",
        "webpack-dev-server": "^3.11.0"
      }
    }

     ###### ###### ###### ###### ###### ###### ###### ###### ###### ###### ######

    接入typescript

    npm install -D @types/react @types/react-dom @types/react-router-dom @types/react-redux typescript ts-loader
    npm install -g typescript
    tsc -init

    修改生成的tsconfig.json

    {
      "compilerOptions": {
        "jsx": "react",
        "target": "es5", 
        "module": "commonjs", 
        "sourceMap": true,                     
        "removeComments": true,                
        "strict": true, 
        "noImplicitAny": true,                 
        "esModuleInterop": true, 
        "skipLibCheck": true, 
        "forceConsistentCasingInFileNames": true 
      },
      "include": [
        "src/**/*"
      ],
      "exclude": [
        "node_modules",
        "build",
      ]
    }

    将src/models/*/index.js 都改为index.ts并加入相应变量类型

    // src/models/actions/index.ts
    export const CREATE_TODO: string = 'CREATE'; // 增加一个todo
    export const DELETE_TODO: string = 'DELETE'; // 删除一个todo
    export const CREATE_TYPE: string = 'CREATE_TYPE'; // 添加操作
    export const DELETE_TYPE: string = 'DELETE_TYPE'; // 删除操作
    // src/models/reducers/index.ts
    import {
        CREATE_TODO,
        DELETE_TODO,
        CREATE_TYPE,
        DELETE_TYPE,
    } from '../actions'
    
    interface TodoAction {
        type: string;
        id: number;
        text: string;
    }
    interface OperateAction {
        type: string;
    }
    export interface TodoState {
        id: number;
        text: string;
        completed: boolean;
    }
    export interface OperateState {
        createCounter: number;
        deleteCounter: number;
    }
    
    export function todos(state: TodoState[] = [], action: TodoAction) {
        switch (action.type) {
            case CREATE_TODO: {
                return [...state, { id: action.id, text: action.text, completed: false }]
            }
            case DELETE_TODO: {
                return [...state].filter(({ id }) => id !== action.id)
            }
            default: {
                return state;
            }
        }
    }
    
    export function operateCounter(state: OperateState = { createCounter: 0, deleteCounter: 0 }, action: OperateAction) {
        const { createCounter, deleteCounter } = state;
        switch (action.type) {
            case CREATE_TYPE: {
                return { ...state, createCounter: createCounter + 1 }
            }
            case DELETE_TYPE: {
                return { ...state, deleteCounter: deleteCounter + 1 }
            }
            default: {
                return state;
            }
        }
    }
    // src/models/stores/index.ts
    import { combineReducers, createStore } from 'redux'
    import * as reducers from '../reducers'
    
    const todoApp = combineReducers(reducers)
    export default createStore(todoApp)

    将src/index.js 改为src/index.tsx,并添加相应接口,指定变量类型

    // src/index.tsx
    import React from 'react'
    import { render } from 'react-dom'
    import {
        HashRouter,
        Route,
        Switch,
        Redirect,
    } from 'react-router-dom'
    import { Provider, connect } from 'react-redux'
    import { Dispatch } from 'redux'
    import store from './models/stores'
    import {
        CREATE_TODO,
        DELETE_TODO,
        CREATE_TYPE,
        DELETE_TYPE,
    } from './models/actions'
    import { TodoState, OperateState } from './models/reducers'
    import styles from './index.less'
    
    interface HomeProps {
        todos: TodoState[];
        operateCounter: OperateState;
        dispatch: Dispatch;
    }
    
    const HomeOld: React.FC<HomeProps> = (props) => {
        const {
            todos = [],
            operateCounter: {
                createCounter = 0,
                deleteCounter = 0,
            },
        } = props;
        return (
            <>
                <div>HOME HOHOHO</div>
                <div>当前todos如下,可以在page1与page2中操作todos列表:</div>
                <div className={styles.hohoho}>添加操作: {createCounter} 次,删除操作: {deleteCounter} 次</div>
                {todos.map(({ text, id }) => (<li key={id}>{`id:${id}-text:${text}`}</li>))}
            </>
        )
    }
    const mapStateToPropsHome = (state: HomeProps) => {
        return {
            todos: state.todos,
            operateCounter: state.operateCounter,
        };
    };
    const Home = connect(mapStateToPropsHome)(HomeOld);
    
    
    
    
    
    const Page1Old: React.FC<HomeProps> = (props) => {
        const { todos = [], dispatch } = props;
        let input: HTMLInputElement | null;
        function onClick() {
            const { id = 0 } = [...todos].pop() || {};
            dispatch({
                type: CREATE_TODO,
                id: id + 1,
                text: (input as HTMLInputElement).value,
            });
            dispatch({ type: CREATE_TYPE });
        }
        return (
            <>
                <div>PAGE1 HOHOHO</div>
                <input ref={node => { input = node }} />
                &nbsp;&nbsp;
                <button onClick={onClick}>添加</button>
                {todos.map(({ text, id }) => (<li key={id}>{`id:${id}-text:${text}`}</li>))}
            </>
        )
    }
    const mapStateToPropsPage1 = (state: HomeProps) => {
        return {
            todos: state.todos,
        };
    };
    const Page1 = connect(mapStateToPropsPage1)(Page1Old);
    
    
    
    
    
    const Page2Old: React.FC<HomeProps> = (props) => {
        const { todos = [], dispatch } = props;
        function onClick(id: number) {
            dispatch({
                type: DELETE_TODO,
                id,
            });
            dispatch({ type: DELETE_TYPE });
        }
        return (
            <>
                <div>PAGE2 HOHOHO</div>
                {todos.map(({ text, id }) => (
                    <li key={id}>
                        {`id:${id}-text:${text}`}
                        &nbsp;&nbsp;
                        <a href="javascript:;" onClick={onClick.bind(null, id)}>删除该项</a>
                    </li>
                ))}
            </>
        )
    }
    const mapStateToPropsPage2 = (state: HomeProps) => {
        return {
            todos: state.todos,
        };
    };
    const Page2 = connect(mapStateToPropsPage2)(Page2Old);
    
    
    
    
    
    
    const App = () => (
        <Provider store={store}>
            <div className={styles.hohoho}>STAGE HOHOHO</div>
            <li><a href='#/home'>去home</a></li>
            <li><a href='#/page1'>去page1</a></li>
            <li><a href='#/page2'>去page2</a></li>
            <hr />
            <HashRouter>
                <Switch>
                    <Route exact path='/home' component={Home} />
                    <Route exact path='/page1' component={Page1} />
                    <Route exact path='/page2' component={Page2} />
                    <Redirect from='/' to='/home' />
                </Switch>
            </HashRouter>
        </Provider>
    )
    
    render(<App />, document.getElementById('root'))

    同时需要修改build/webpack.config.js,修改入口文件将原来的index.js改为index.tsx,添加resolve配置

    // build/webpack.config.js
    const path = require('path');
    const HtmlWebpackPlugin = require('html-webpack-plugin');
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');
    
    module.exports = {
        entry: './src/index.tsx',
        output: {
            filename: 'bundle.js',
            path: path.resolve(__dirname, 'dist'),
        },
        resolve: {
            extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
        },
        devServer: {
            port: 3001,
        },
        devtool: 'inline-source-map',
        plugins: [
            new CleanWebpackPlugin(),
            new HtmlWebpackPlugin({
                template: './src/index.html',
            })
        ],
        module: {
            rules: [
                {
                    test: /.tsx?$/,
                    exclude: /node_modules/,
                    loader: 'ts-loader',
                },
                {
                    test: /.js$/,
                    exclude: /node_modules/,
                    loader: 'babel-loader',
                },
                {
                    test: /.less$/,
                    exclude: /node_modules/,
                    use: [
                        'style-loader',
                        { loader: 'css-loader', options: { modules: true } },
                        'less-loader',
                    ]
                },
            ]
        }
    };
    cd ../../ && mkdir types && cd types && touch global.d.ts // 在src目录下创建types文件夹添加global.d.ts文件
    // src/types/global.d.ts
    declare module '*.svg'
    declare module '*.png'
    declare module '*.jpg'
    declare module '*.jpeg'
    declare module '*.gif'
    declare module '*.bmp'
    declare module '*.tiff'
    declare module '*.less'

     重启服务

  • 相关阅读:
    知方可补不足~Sqlserver发布订阅与sql事务的关系
    基础才是重中之重~泛型类的静态构造方法可不是只执行一次呀
    EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~终结~配置的优化和事务里读写的统一
    EF架构~通过EF6的DbCommand拦截器来实现数据库读写分离~再续~添加对各只读服务器的心跳检测
    windows服务的创建、安装和调试
    Axure RP 实践.1
    实习第一天之数据绑定:<%#Eval("PartyName")%>'
    Hadoop云计算大数据书籍分享
    Android利用setLayoutParams在代码中调整布局(Margin和居中)
    TopCoder中插件的用法
  • 原文地址:https://www.cnblogs.com/mapingchuan/p/13262685.html
Copyright © 2011-2022 走看看