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'

     重启服务

  • 相关阅读:
    CodeForces 7B
    CodeForces 4D
    离散化
    线段树入门
    洛谷 P3951 小凯的疑惑(赛瓦维斯特定理)
    Codeforces 1295D Same GCDs (欧拉函数)
    Codeforces 1295C Obtain The String (二分)
    Codeforces 1295B Infinite Prefixes
    Codeforces 1295A Display The Number(思维)
    Codeforces 1294F Three Paths on a Tree(树的直径,思维)
  • 原文地址:https://www.cnblogs.com/mapingchuan/p/13262685.html
Copyright © 2011-2022 走看看