基于Webpack4和React hooks搭建项目的方法


Posted in Javascript onFebruary 05, 2019

面对日新月异的前端,我表示快学不动了:joy:。 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

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

<Context.Provider value={{ state, dispatch }}>基本代替了 react-redux 的 ** `。

// 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);
  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.js, action.js 和 reducer.js 与之前的写法是一致的。

// constants.js
export const ADD_COMMENT = 'ADD_COMMENT'
export const REMOVE_COMMENT = 'REMOVE_COMMENT'
// 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

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

import { combineReducers } from 'redux'
import list from './list.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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
用js+xml自动生成表格的东西
Dec 21 Javascript
js取得url地址参数实例
Feb 22 Javascript
js文件缓存之版本管理详解
Jul 05 Javascript
JS图片切换的具体方法(带缩略图版)
Nov 12 Javascript
JavaScript中的console.group()函数详细介绍
Dec 29 Javascript
JavaScript生成带有缩进的表格代码
Jun 15 Javascript
jquery代码规范让代码越来越好看
Feb 03 Javascript
vue2的todolist入门小项目的详细解析
May 11 Javascript
vue 怎么创建组件及组件使用方法
Jul 27 Javascript
vue工程全局设置ajax的等待动效的方法
Feb 22 Javascript
解决vue里a标签值解析变量,跳转页面,前面加默认域名端口的问题
Jul 22 Javascript
JavaScript实现网页留言板功能
Nov 23 Javascript
利用Dectorator分模块存储Vuex状态的实现
Feb 05 #Javascript
小程序页面动态配置实现方法
Feb 05 #Javascript
PHP实现基于Redis的MessageQueue队列封装操作示例
Feb 02 #Javascript
AngularJS实现的自定义过滤器简单示例
Feb 02 #Javascript
vue实现的树形结构加多选框示例
Feb 02 #Javascript
javascript中floor使用方法总结
Feb 02 #Javascript
JS对象和字符串之间互换操作实例分析
Feb 02 #Javascript
You might like
destoon出现验证码不显示时的紧急处理方法
2014/08/22 PHP
php中JSON的使用与转换
2015/01/14 PHP
面向对象Javascript核心支持代码分享
2012/05/23 Javascript
js批量设置样式的三种方法不推荐使用with
2013/02/25 Javascript
jQuery判断密码强度实现思路及代码
2013/04/24 Javascript
javascript中声明函数的方法及调用函数的返回值
2014/07/22 Javascript
node.js中的fs.utimes方法使用说明
2014/12/15 Javascript
原生javaScript实现图片延时加载的方法
2014/12/22 Javascript
浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
2016/01/19 Javascript
DeviceOne 让你一见钟情的App快速开发平台
2016/02/17 Javascript
关于JS中match() 和 exec() 返回值和属性的测试
2016/03/21 Javascript
JS产生随机数的用法小结
2016/12/10 Javascript
springMVC + easyui + $.ajaxFileUpload实现文件上传注意事项
2017/04/23 Javascript
微信小程序自定义tab实现多层tab嵌套功能
2018/06/15 Javascript
vue实现鼠标经过动画
2019/10/16 Javascript
全面解析JavaScript Module模式
2020/07/24 Javascript
解决js中的setInterval清空定时器不管用问题
2020/11/17 Javascript
如何在JavaScript中等分数组的实现
2020/12/13 Javascript
[45:52]2018DOTA2亚洲邀请赛 4.1小组赛 A组加赛 LGD vs Liquid
2018/04/02 DOTA
浅谈Python2之汉字编码为unicode的问题(即类似\xc3\xa4)
2019/08/12 Python
Python爬虫爬取Bilibili弹幕过程解析
2019/10/10 Python
编写python代码实现简单抽奖器
2020/10/20 Python
塔吉特百货公司官网:Target
2017/04/27 全球购物
巴西独家产品和现场演示购物网站:Shoptime
2019/07/11 全球购物
英国Flybe航空官网:欧洲最大的独立支线廉价航空公司
2019/07/15 全球购物
在C中是否有模拟继承等面向对象程序设计特性的好方法
2012/05/22 面试题
const char*, char const*, char*const的区别是什么
2014/07/09 面试题
计算机应用专业推荐信
2013/11/13 职场文书
财务管理个人自荐书范文
2013/11/24 职场文书
汽车队司机先进事迹材料
2014/02/01 职场文书
出国留学英文自荐信
2015/03/25 职场文书
消防宣传语大全
2015/07/13 职场文书
世界文化遗产导游词
2019/08/07 职场文书
如何在向量化NumPy数组上进行移动窗口
2021/05/18 Python
Unity连接MySQL并读取表格数据的实现代码
2021/06/20 MySQL
Apache POI的基本使用详解
2021/11/07 Servers