基于 Webpack 4 和 React hooks 搭建项目

面对日新月异的前端,我表示快学不动了?。 Webpack 老早就已经更新到了 V4.x,前段时间 React 又推出了 hooks API。刚好春节在家里休假,时间比较空闲,还是赶紧把 React技术栈这块补上。

网上有很多介绍 hooks 知识点的文章,但都比较零碎,基本只能写一些小 Demo。还没有比较系统的,全新的基于 hooks 进行搭建实际项目的讲解。所以这里就从开发实际项目的角度,搭建起单页面 Web App项目的基本脚手架,并基于 hooks API 实现一个 react 项目模版。

Hooks 最吸引人的地方就是用 函数式组件 代替面向对象的 类组件。此前的 react 如果涉及到状态,解决方案通常只能使用 类组件,业务逻辑一复杂就容易导致组件臃肿,模块的解藕也是个问题。而使用基于 hooks函数组件 后,代码不仅更加简洁,写起来更爽,而且模块复用也方便得多,非常看好它的未来。

webpack 4 的配置

没有使用 create-react-app 这个脚手架,而是从头开始配置开发环境,因为这样自定义配置某些功能会更方便些。下面这个是通用的配置 webpack.common.js 文件。

const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const { HotModuleReplacementPlugin } = require('webpack');

module.exports = {
    entry: './src/index.js',//单入口
    output: {
        path: resolve(__dirname, 'dist'),
        filename: '[name].[hash].js'//输出文件添加hash
    },
    optimization: { // 代替commonchunk, 代码分割
        runtimeChunk: 'single',
        splitChunks: {
            cacheGroups: {
                vendor: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendors',
                    chunks: 'all'
                }
            }
        }
    },
    module: {
        rules: [
            {
                test: /\.jsx?$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            },
            {
                test: /\.css$/,
                use: ['style-loader', 'css-loader']
            },
            {
                test: /\.scss$/,
                use: ['style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            importLoaders: 1,
                            modules: true,//css modules
                            localIdentName: '[name]___[local]___[hash:base64:5]'
                        },
                    },
                    'postcss-loader', 'sass-loader']
            },
            {   /* 
                当文件体积小于 limit 时,url-loader 把文件转为 Data URI 的格式内联到引用的地方
                当文件大于 limit 时,url-loader 会调用 file-loader, 把文件储存到输出目录,并把引用的文件路径改写成输出后的路径 
                */
                test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
                use: [{
                    loader: 'url-loader',
                    options: {
                        limit: 1000
                    }
                }]
            }
        ]
    },
    plugins: [
        new CleanWebpackPlugin(['dist']),//生成新文件时,清空生出目录
        new HtmlWebpackPlugin({
            template: './public/index.html',//模版路径
            favicon: './public/favicon.png',
            minify: { //压缩
                removeAttributeQuotes:true,
                removeComments: true,
                collapseWhitespace: true,
                removeScriptTypeAttributes:true,
                removeStyleLinkTypeAttributes:true
             },
        }),
        new HotModuleReplacementPlugin()//HMR
    ]
};

接着基于 webpack.common.js 文件,配置出开发环境的 webpack.dev.js 文件,主要就是启动开发服务器。

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

module.exports = merge(common, {
    mode: 'development',
    devtool: 'inline-source-map',
    devServer: {
        contentBase: './dist',
        port: 4001,
        hot: true
    }
});

生成模式的 webpack.prod.js 文件,只要定义了 mode:'production'webpack 4 打包时就会自动压缩优化代码。

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

module.exports = merge(common, {
  mode: 'production',
  devtool: 'source-map'
});

配置 package.js 中的 scripts

{
  "scripts": {
     "start": "webpack-dev-server --open --config webpack.dev.js",
     "build": "webpack --config webpack.prod.js"
  }
}

Babel 的配置

babel.babelrc 文件, css module 包这里推荐 babel-plugin-react-css-modules
react-css-modules 既支持全局的css(默认 className 属性),同时也支持局部css module( styleName 属性),还支持css预编译器,这里使用的是 scss

{
    "presets": [
        "@babel/preset-env",
        "@babel/preset-react"
    ],
    "plugins": [
        "@babel/plugin-proposal-class-properties",
        "@babel/plugin-transform-runtime",
        [
            "react-css-modules",
            {
                "exclude": "node_modules",
                "filetypes": {
                    ".scss": {
                        "syntax": "postcss-scss"
                    }
                },
                "generateScopedName": "[name]___[local]___[hash:base64:5]"
            }
        ]
    ]
}

React 项目

下面是项目基本的目录树结构,接着从入口开始一步步细化整个项目。

├ package.json
├ src
│ ├ component // 组件目录
│ ├ reducer   // reducer目录
│ ├ action.js
│ ├ constants.js
│ ├ context.js
│ └ index.js
├ public // 静态文件目录
│ ├ css
│ └ index.html
├ .babelrc
├ webpack.common.js
├ webpack.dev.js
└ webpack.prod.js

状态管理组件使用 reduxreact-router 用于构建单页面的项目,因为使用了 hooks API,所以不再需要 react-redux 连接状态 state

入口文件 index.js

// index.js
import React, { useReducer } from 'react'
import { render } from 'react-dom'
import { HashRouter as Router, Route, Redirect, Switch } from 'react-router-dom'
import Context from './context.js'
import Home from './component/home.js'
import List from './component/list.js'
import rootReducer from './reducer'
import '../public/css/index.css'

const Root = () => {
    const initState = {
        list: [
            { id: 0, txt: 'webpack 4' },
            { id: 1, txt: 'react' },
            { id: 2, txt: 'redux' },
        ]
    };
    // useReducer映射出state,dispatch
    const [state, dispatch] = useReducer(rootReducer, initState);
    // <Context.Provider value={{ state, dispatch }}> 基本代替了 react-redux 的 <Provider store={store}>
    return <Context.Provider value={{ state, dispatch }}>
        <Router>
            <Switch>
                <Route exact path="/" component={Home} />
                <Route exact path="/list" component={List} />
                <Route render={() => (<Redirect to="/" />)} />
            </Switch>
        </Router>
    </Context.Provider>
}
render(
    <Root />,
    document.getElementById('root')
)

constants.jsaction.jsreducer.js 与之前的写法是一致的。

// constants.js
export const ADD_COMMENT = 'ADD_COMMENT'
export const REMOVE_COMMENT = 'REMOVE_COMMENT'

action.js

// action.js
import { ADD_COMMENT, REMOVE_COMMENT } from './constants'

export function addComment(comment) {
  return {
    type: ADD_COMMENT,
    comment
  }
}

export function removeComment(id) {
  return {
    type: REMOVE_COMMENT,
    id
  }
}

list.js

//list.js
import { ADD_COMMENT, REMOVE_COMMENT } from '../constants.js'

const list = (state = [], payload) => {
    switch (payload.type) {
        case ADD_COMMENT:
            if (Array.isArray(payload.comment)) {
                return [...state, ...payload.comment];
            } else {
                return [...state, payload.comment];
            }
        case REMOVE_COMMENT:
            return state.filter(i => i.id != payload.id);
        default: return state;
    }
};
export default list

reducer.js

//reducer.js
import { combineReducers } from 'redux'
import list from './list.js'
import user from './user.js'

const rootReducer = combineReducers({
  list,
  user
});

export default rootReducer

最大区别的地方就是 component 组件,基于 函数式,内部的表达式就像是即插即用的插槽,可以很方便的抽取出通用的组件,然后从外部引用。相比之前的 面向对象 方式,我觉得 函数表达式 更受前端开发者欢迎。

  • useContext 获取全局的 state
  • useRef 代替之前的 ref
  • useState 代替之前的 state
  • useEffect 则可以代替生命周期钩子函数

    //监控数组中的参数,一旦变化就执行
    useEffect(() => { updateData(); },[id]);
    
    //不传第二个参数的话,它就等价于每次componentDidMount和componentDidUpdate时执行
    useEffect(() => { updateData(); });
    
    //第二个参数传空数组,等价于只在componentDidMount和componentWillUnMount时执行, 
    //第一个参数中的返回函数用于执行清理功能
    useEffect(() => { 
        initData(); 
        reutrn () => console.log('componentWillUnMount cleanup...'); 
    }, []);

最后就是实现具体界面和业务逻辑的组件了,下面是其中的List组件

// list.js
import React, { useRef, useState, useContext } from 'react'
import { bindActionCreators } from 'redux'
import { Link } from 'react-router-dom'
import Context from '../context.js'
import * as actions from '../action.js'
import Dialog from './dialog.js'
import './list.scss'

const List = () => {
    const ctx = useContext(Context);//获取全局状态state
    const { user, list } = ctx.state;
    const [visible, setVisible] = useState(false);
    const [rid, setRid] = useState('');
    const inputRef = useRef(null);
    const { removeComment, addComment } = bindActionCreators(actions, ctx.dispatch);

    const confirmHandle = () => {
        setVisible(false);
        removeComment(rid);
    }

    const cancelHandle = () => {
        setVisible(false);
    }

    const add = () => {
        const input = inputRef.current,
            val = input.value.trim();
        if (!val) return;
        addComment({
            id: Math.round(Math.random() * 1000000),
            txt: val
        });
        input.value = '';
    }

    return <>
        <div styleName="form">
            <h3 styleName="sub-title">This is list page</h3>
            <div>
                <p>hello, {user.name} !</p>
                <p>your email is {user.email} !</p>
                <p styleName="tip">please add and remove the list item !!</p>
            </div>
            <ul> {
                list.map(l => <li key={l.id}>{l.txt}<i className="icon-minus" title="remove item" onClick={() => {
                    setVisible(true);
                    setRid(l.id);
                }}></i></li>)
            } </ul>
            <input ref={inputRef} type="text" />
            <button onClick={add} title="add item">Add Item</button>
            <Link styleName="link" to="/">redirect to home</Link>
        </div>
        <Dialog visible={visible} confirm={confirmHandle} cancel={cancelHandle}>remove this item ?</Dialog>
    </>
}

export default List;

项目代码

https://github.com/edwardzhong/webpack_react

转载于:https://www.cnblogs.com/edwardloveyou/p/10351697.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,首先你需要安装webpack5、react、typescript、eslint以及相关的loader和插件。你可以通过以下命令安装它们: ``` npm install webpack webpack-cli webpack-dev-server react react-dom @types/react @types/react-dom typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-config-airbnb eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks prettier -D ``` 接下来,你需要创建一个webpack配置文件,可以命名为`webpack.config.js`,在该文件中配置webpack相关内容: ```javascript const path = require('path'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.tsx', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js', }, resolve: { extensions: ['.tsx', '.ts', '.js'], }, module: { rules: [ { test: /\.(ts|tsx)$/, exclude: /node_modules/, use: [ { loader: 'babel-loader', options: { presets: [ '@babel/preset-env', '@babel/preset-react', '@babel/preset-typescript', ], }, }, { loader: 'ts-loader', }, ], }, { test: /\.(js|jsx)$/, exclude: /node_modules/, use: ['babel-loader'], }, { test: /\.css$/, use: ['style-loader', 'css-loader'], }, ], }, plugins: [ new HtmlWebpackPlugin({ template: './public/index.html', }), ], devtool: 'inline-source-map', devServer: { contentBase: './dist', port: 3000, }, mode: 'development', }; ``` 在上面的配置中,我们指定了入口文件为`src/index.tsx`,打包后的输出文件为`dist/bundle.js`。我们还通过resolve属性指定了文件的拓展名,这样在引入文件时就不用指定拓展名了。 在module属性中,我们定义了不同类型文件的loader,例如对于`.tsx`和`.ts`文件,我们使用了`babel-loader`和`ts-loader`,对于`.js`和`.jsx`文件,我们只使用了`babel-loader`。此外,我们还使用了`style-loader`和`css-loader`处理`.css`文件。 在plugins属性中,我们使用了`HtmlWebpackPlugin`插件,用于生成HTML文件。在devServer属性中,我们指定了开发服务器的端口和从哪个文件夹提供内容。 最后,我们使用了`inline-source-map`作为开发模式下的source-map,将模式设置为`development`。 接下来,你需要创建一个`.babelrc`文件,用于配置babel: ```json { "presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"] } ``` 然后,你需要创建一个`.eslintrc.json`文件,用于配置eslint: ```json { "parser": "@typescript-eslint/parser", "plugins": ["@typescript-eslint"], "extends": ["airbnb", "plugin:@typescript-eslint/recommended", "prettier"], "rules": { "prettier/prettier": ["error"], "react/jsx-filename-extension": [1, { "extensions": [".tsx"] }], "import/extensions": ["error", "never", { "svg": "always" }] }, "settings": { "import/resolver": { "node": { "extensions": [".js", ".jsx", ".ts", ".tsx"] } } } } ``` 在上面的配置中,我们使用了`@typescript-eslint/parser`解析Typescript代码,并使用了`@typescript-eslint/eslint-plugin`插件提供的规则。我们还继承了`eslint-config-airbnb`和`eslint-config-prettier`,并使用了一些自定义的规则。 最后,你需要在`package.json`中添加一些scripts,用于启动开发服务器和打包代码: ```json { "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production" } } ``` 现在你就可以使用`npm start`命令启动开发服务器,使用`npm run build`命令打包代码了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值