GVKun编程网logo

基于 Webpack 4 和 React hooks 搭建项目(webpack配置react)

5

在本文中,我们将带你了解基于Webpack4和Reacthooks搭建项目在这篇文章中,我们将为您详细介绍基于Webpack4和Reacthooks搭建项目的方方面面,并解答webpack配置reac

在本文中,我们将带你了解基于 Webpack 4 和 React hooks 搭建项目在这篇文章中,我们将为您详细介绍基于 Webpack 4 和 React hooks 搭建项目的方方面面,并解答webpack配置react常见的疑惑,同时我们还将给您一些技巧,以帮助您实现更有效的ES6+React+Webpack初步构建项目流程、React-APP结合webpack搭建项目、React入门:从项目搭建(webpack)到引入路由(react-router)和状态管理(react-redux/saga)、React项目搭建webpack+TS

本文目录一览:

基于 Webpack 4 和 React hooks 搭建项目(webpack配置react)

基于 Webpack 4 和 React hooks 搭建项目(webpack配置react)

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

ES6+React+Webpack初步构建项目流程

ES6+React+Webpack初步构建项目流程

当我们打算使用Webpack构建工具,React和ES6来开发项目的时候,构建这么一套自动化的项目的流程见下:

第一步:webpack是一个基于node的项目,我们使用命令行对webpack进行全局的安装

npm install webpack –g。可以通过webpack –h来查看安装的版本信息。然后我们新建一个文件夹用来存放整个项目文件。为了可以在项目中使用webpack,我们需要将webpack安装到当前的项目依赖中,在新建的文件夹下输入:npm init(安装webpack依赖,会在对应文件夹下生成package.json),然后使用npm install webpack –save-dev将webpack的模块添加到本地项目中。此时会生成一个本地的模块目录node_modules。如下图所示:


第二步:在新建的文件下目录下创建你的工程目录结构。为了演示react和es6,我这里需要展示一个背景色为橙色,只显示一段文字:hello!的工程。首先创建一个index.html,入口文件app.js,被引用的UI模块文件hello.js以及对应的样式文件style.css。目录结构如下所示:

第三步:这里定义的入口js文件app.js中可以使用es6中的import引用hello.js中的UI组件。同时也可以使用require来引入定义的style.css文件用来改变元素的样式属性。对应的在hello.js中可以使用react创建对应的功能模块的组件,并通过export导出接口给其他文件使用。



第四步:创建并使用webpack最重要的配置文件webpack.config.js。这个文件主要定义webpack构建工具识别的入口文件entry,出口文件output(这里的出口文件主要是用来被index.html中的script标签进行引用)以及webpack为了打包对应css,img,es6所需要的加载器loader。这里的webpack.config.js是webpack默认运行时加载的配置文件。


TIPS:这里为了加载使用各个loader,比如说css-loader,babel-loader,style-loader等等加载器,需要我们在开发项目之前进行安装。有两个安装方式:

A.可以直接在命令行输入对应安装命令:npm install style-loader –save-dev之类

B.直接在package.json中的devDependencies定义所需要使用的loader加载器名字,然后在命令行直接输入npm install进行对应loader的安装。
 
 
这里为了可以使用webpack的更具文件的改动直接自动刷新浏览器的功能,安装并使用了webpack-dev-server;注意在使用webpack-devserver的时候一定要在后面加上参数—hot和—inline才能达到自动刷新浏览器的目的。

React-APP结合webpack搭建项目

React-APP结合webpack搭建项目

React-APP搭建项目

React有三种安装的方式,想了解的登录React官网查看,今天介绍的一个自动安装的不需要怎么配置环境,自动生成的方式,类似于vue-cli!并实现一个小案例,留言功能!

官网的安装教程!

安装react-app

//依次安装
npm install -g create-react-app
create-react-app my-app

cd my-app
npm start

最终启动自后会出现一个Welcome React页面,
大家可以去试试.

下边我们配置一下项目结构

安装我们需要的loader

npm file-loader url url-loader --save-dev

配置webpack

rules: [
     {test: /\.js$/,exclude: /node_modules/,loader: "react-hot-loader!babel-loader"},{test: /\.css$/,loader: "style-loader!css-loader"},{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,loader: "file-loader" },{ test: /\.(woff|woff2)$/,loader:"url-loader?prefix=font/&limit=5000" },{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,loader: "url-loader?limit=10000&mimetype=application/octet-stream" },{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,loader: "url-loader?limit=10000&mimetype=image/svg+xml" }
  ]

复制之前的webpack.config.js文件

我们加以改造

module.exports = {
  entry: './index.js',output: {
    path:path.resolve(__dirname,"build"),publicPath:"/assets/",filename: 'bundle.js'
  },module: {
      rules: [
    { test: /\.js$/,loader: "react-hot-loader!babel-loader" },{ test: /\.css$/,loader: "style-loader!css-loader" },//添加
    { test: /\.(woff|woff2)$/,//添加
    { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,//添加
    { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,loader: "url-loader?limit=10000&mimetype=image/svg+xml" } //添加
      ]
    } 
}

导入我们之前的json文件

{
  "name": "myapp","version": "0.1.0","private": true,"dependencies": {
    "antd-mobile": "^1.5.0","react": "^15.6.1","react-dom": "^15.6.1","react-scripts": "1.0.10"
  },"scripts": {
    "start": "react-scripts start","build": "react-scripts build","test": "react-scripts test --env=jsdom","eject": "react-scripts eject"
  },"devDependencies": {
    "babel-plugin-import": "^1.2.1","node-sass": "^4.5.3","sass-loader": "^6.0.6"
  }
}

输入npm install补全我们的项目结构

npm install

引入bootstap模块

我们的bootstap是事先安装完成之后,把他移动到css项目目录下的,index配置如下

//入口文件index.js下
import React from 'react';
import ReactDOM from 'react-dom';
import LiuYanapp from './LiuYanapp';

import './bootstrap/css/bootstrap.min.css';  //引入样式文件
ReactDOM.render(<LiuYanapp/>,document.getElementById("app"));      //输出到页面

创建留言模板,并划分项目模块

import React from 'react';

import LiuYanList from './LiuYanList';
import LiuYanForm from './LiuYanForm';

class LiuYanapp extends React.Component{
    constructor(props){
        super(props);
        this.ids=1;
        this.state={
                todos:[]
        };
        
        this.addItem=this.addItem.bind(this);
        this.deleteItem=this.deleteItem.bind(this);
    }
    deleteItem(id){
        let newtodos=this.state.todos.filter((item)=>{
            return !(item.id==id)
        });
          this.setState({
            todos:newtodos
        });

    }

    addItem(value){
       this.state.todos.unshift(
            {
                id:this.ids++,text:value,time:(new Date()).toLocaleString(),done:0
            }
       )
        this.setState({
            todos:this.state.todos
        });   
    }

    render(){
        return (
            <div className="container">
                <br/>
                <br/>
                <br/>
                <br/>
                <div className="panel panel-default">
                    <div className="panel-headingbg-danger">
                         
                            <hr/>
                              
                    </div>
                    <div className="panel-body">           
                    
                             <h1 className="text-center ">留言板</h1>
                             <LiuYanList deleteItem={this.deleteItem} data={this.state.todos}/>
                             <LiuYanForm addItem={this.addItem}/> 
                    </div>
                </div> 
            </div>         
        );
    }
}

export default LiuYanapp;

创建LiuYanForm.js文件

import React from 'react';

class LiuYanForm extends React.Component{
   tijiao(event){
        event.preventDefault();
    }
    add(event){        
        if(event.type=="keyup"&&event.keyCode!=13){
            return false;
        }
        let txt=this.refs.txt.value;
        if(txt=="") return false;       
        this.props.addItem(txt);
        this.refs.txt.value="";
    }
    render(){
        return(
             <form className="form-horizontal" onSubmit={this.tijiao.bind(this)}>
                <div className="form-group">
                    <div className="col-sm-10">
                        <input ref="txt"  type="text" className="form-control" onKeyUp={this.add.bind(this)} id="exampleInputName2" placeholder="请输入留言内容"/>
                    </div>
                    <div className="col-sm-2">
                      <button type="button" className="btn btn-primary" onClick={this.add.bind(this)}>留言</button>
                    </div>               
                </div>           
            </form>
        );
    }
}
export default LiuYanForm;

创建LiuYanItem.js文件

import React from 'react';

class LiuYanItem extends React.Component{
    delete(){
        this.props.deleteItem(this.props.data.id);
    }
    render(){

        let {text,time,done,id}=this.props.data;

        return (
           <tr>
               <td>{text}
                   <br/>
                   <br/>
               {time}
               </td>
               <td>
                   <a className="btn glyphicon glyphicon-remove btn-danger" onClick={this.delete.bind(this)}>删除留言</a>
                </td>
           </tr>
        );
    }
}

export default LiuYanItem;

创建LiuYanList.js文件

import React from 'react';

import LiuYanItem from './LiuYanItem';
class LiuYanList extends React.Component{
    render(){
        let todos=this.props.data;
       
        let todoItems=todos.map(item=>{
            return <LiuYanItem deleteItem={this.props.deleteItem} key={item.id} data={item}/>
        });
        
        return (
            <table className="table table-striped">
                <thead>
                    <tr>
                        <th>留言</th>                       
                    </tr>
                </thead>
                <tbody>
                    {todoItems}
                </tbody>              
            </table>
        );
    }
}

export default LiuYanList;

这样我们的留言功能就创建完了,大家输入npm start就可以对项目进行启动!想了解的同学们可以复制回去实验一下!

React入门:从项目搭建(webpack)到引入路由(react-router)和状态管理(react-redux/saga)

React入门:从项目搭建(webpack)到引入路由(react-router)和状态管理(react-redux/saga)

一、什么是React

React是什么?React的官网是这样介绍的:
React-用于构建用户界面的 JavaScript 库。
看起来十分简洁,React仅仅是想提供一个构建用户界面的工具,也就是只关心UI层面,不关心数据层面,数据层面的东西交给专门的人(react-redux)来做。所以有些人会说React就是一个ui框架。
React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合,比如,在 UI 中需要绑定处理事件、在某些时刻状态发生变化时需要通知到 UI,以及需要在 UI 中展示准备好的数据。所以,React提供了jsx语法,也可以理解为让你在ul组件里写很多东西。jsx最终是通过babel将组件转化为React.createElement()函数来调用。组件化开发的乐趣需要慢慢体会。既然是入门,下面通过搭建项目-小例子-引入路由/状态管理简单介绍下react,有关复杂的概念这里暂不提及。

const name = ''Josh Perez'';
const element = <h1>Hello, {name}</h1>;

ReactDOM.render(
  element,
  document.getElementById(''root'')
);

二、项目搭建

上面说了,react的组件开发是需要babel进行编译的,浏览器是不能直接解析的,所以react项目一般都是需要做一些简单的配置的。当然,有很多的配置完善的脚手架可以使用,比如官方脚手架creat-react-app或者蚂蚁金服的dva框架,使用起来都是很方便上手。但是咱们我觉得通过简单搭建一个项目可以更好的理解react。下面将会通过webpack一步一步搭建一个可以使用的项目,但是具体的优化这里就先不考虑了。

1.打开终端、创建项目

mkdir react-demo && cd react-demo //创建文件夹,并进入文件夹

2.初始化构建

npm init //为了后面下载npm包和node配置使用
         //一路回车键就可以了!项目中多出来一个package.json文件

3.创建项目入口

新建app.js文件,引入react和react-dom,新建一个index.html,包含<div id=''app''></div>。

    import React from ''react''; // 终端执行 npm i react 下载react
    import ReactDom from ''react-dom'' // 终端执行 npm i react-dom 下载react-dom
    
    function App(){ //以函数的方式创建react组件
        return <div>welcom,react-app</div>
    }
    
    ReactDom.render( //将组件App渲染挂载到页面的根节点app
        <App />,
        document.getElementById(''app'')//所以需要新建一个html文件提供app节点供组件挂载
    )

3.webpack配置

创建好入口文件app.js后,需要将jsx语法文件通过babel编译,所以先引入webpack,并新建js文件webpack.config.js做相关的配置如下:

npm i webpack webpack-cli --save //安装webpack及cli依赖

webpack.config.js文件内容:

const path = require("path");
const {CleanWebpackPlugin} = require(''clean-webpack-plugin'');//通过 npm 安装
const HtmlWebpackPlugin = require(''html-webpack-plugin''); //通过 npm 安装
module.exports = {
    entry:{
        app:''./app.js''
    },
    output: {
        path: path.resolve(__dirname, ''./dist''),
        filename: ''[name].bundel.[hash].js''
    },
    module:{
        rules: [{
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use:[{
                loader: ''babel-loader'',
                options: {
                  presets: [
                      ''@babel/preset-env'',//引入babel
                      ''@babel/preset-react'' //引入babel-react 
                    ]
                }
            }]
        }]
    },
    plugins: [
        new CleanWebpackPlugin(),//清空dist文件夹
        new HtmlWebpackPlugin({
            template: ''./index.html''
        }),//添加html模版插件
    ]
}


安装依赖:
npm install babel-loader@8.0.0-beta.0 @babel/core @babel/preset-env @babel/preset-react --save

npm i html-webpack-plugin clean-webpack-plugin --save

4.配置npm/script启动项

打开package.json文件,添加一行script代码,如下:

clipboard.png

此时我们的代码结构如下:

clipboard.png

当我们在终端执行npm run build命令后,可以看到多出来打包后的dist文件夹,以及打包后的js文件,如下:

clipboard.png

在浏览器里打开dist下的index.html文件,OK,第一行react代码闪亮出现!

clipboard.png

4.修改npm/script启动项
上面代码虽然显示出来了,但是不能每次更改代码都执行一次打包吧,太麻烦了?那就搭建一个server吧,在搭配上局部热更新(hmr)使用起来很是舒畅。需要修改3个地方:

1.在webpack.config.js文件中添加以下代代码:
devServer: {//开启一个本地服务
        port: 9000,//端口
        host:"0.0.0.0",//配置后可通过本机ip访问,方便在手机上调试
        hot:true,//
}
2.修改入口文件app.js:
import React from ''react'';
import ReactDOM from ''react-dom''
import {hot} from ''react-hot-loader/root''//添加依赖,通过npm下载

function AppCont(){
    return <div>welcom,react-app hot</div>
}
const App = hot(() =>{//根组件外层通过高阶组件包裹
    return <AppCont />
})

ReactDOM.render(
    <App />,
    document.getElementById(''app'')
)
3.修改package.json文件:增加一个新的script命令dev

"dev": "webpack-dev-server --mode=development --config  webpack.config.js"

执行npm run dev命令,打开http://localhost:9000/,可以看到页面内容出现了,在文字后面添加hot后,发现页面内容更新了,但是没有刷新!ok到这里,简单的项目已经搭建完成,可以进行开发了。

clipboard.png

三、React的一些概念

这里通过一个简单的例子来描述一下react的一些概念。输入框输入数字、点击后面add+数据会改变并求和。

clipboard.png

import React from ''react'';

function Item (props){
    const {count,onChange,onAdd} = props;
    return <div>
        <input value={count} onChange={onChange} />
        <span onClick={onAdd}>add+</span>
    </div>
}

class Add extends React.Component{
    constructor(){
        super();
        this.state = {
            addList : [0,0,0,0]
        }
        this.onChange = this.onChange.bind(this)
        this.onAdd = this.onAdd.bind(this)
    }
    onChange(e,index){
        this.state.addList.splice(index,1,e.target.value)
        this.setState({
            addList:this.state.addList
        })
    }
    onAdd(e){
        this.state.addList.splice(e,1,Number(this.state.addList[e]) + 1 )
        this.setState({
            addList:this.state.addList
        })
    }
    render (){
        const { addList } = this.state;
        return <div>
            {
                addList.map((item,index)=>{
                   return <Item key={index} count={item} onChange={(e)=>{this.onChange(e,index)}} onAdd={()=>{this.onAdd(index)}}  />
                })

            }
            <div>{addList[0]*1 + addList[1]*1 + addList[2]*1 + addList[3]*1}</div>
        </div>
    }
}

export default Add;

麻雀虽小...用来入门也是够用了!

1.react组件的创建的3种方式

1.React.createClass
常用的是class继承和无状态函数的方式,还有一种React.createClass过于繁琐而且会导致不必要的性能开销所以基本上算是被废弃了。
2.无状态组件
直接通过函数声明编写,一般作为纯展示组件使用,内部没有state(可以通过hooks使用),不会实例化,也就不需要分配多余的内存,从而性能得到一定的提升。但是内部无法使用this,也没有生命周期
3.React.Component
React目前极为推宠的创建有状态组件的方式。内部可以使用this和生命周期。可以使用state。
React.Component创建的组件,其成员函数不会自动绑定this,需要手动绑,否则this不能获取当前组件实例对象。绑定方式有3种:可以在构造函数中完成绑定(constructor推荐),也可以在调用时使用method.bind(this)来完成绑定,还可以使用arrow function来绑定。

2.参数传递

一般就是父向子传递参数通过props传递。
子向父传递则要通过父组件传递的函数来传递。
跨组件传递后面会提到使用redux想怎么传就怎么传。

3.受控组件/非受控组件

表单元素除了收到state控制,还会受到用户输入控制。有两个来源,使得内容不可预测。React使用state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
可以看到要给受控组件提供onChange函数来保证state数据的统一性。

三、引入redux

上面看到了父子组件间的传参还很方便的,但是如果要在Add组件外(也就是跨组件传递)使用求和后的数据怎么办?
当然,可以使用增加中间组件、context等方法,这里讲一下大型项目常用的redux状态管理,redux本身的原理也很简单。
1.提供createStore创建store库,将数据都存放在一起,单向数据流。
2.数据只读,修改数据只能通过dispatch方法,传递action对象(必须含有type属性)在纯函数reduce里修改,每一次都是拷贝一个新的数据,不会修改原来的数据源,保证了数据的来源可溯性。

修改app.js
import React from ''react'';
import ReactDOM from ''react-dom'';
import {createStore} from ''redux''; //添加redux依赖后,引入创建store的方法createStore
import {Provider} from ''react-redux'';//添加react-redux依赖,引入改节组件
import reduce from ''./model/reduce'';//createStore方法第一个参数是reduce
import {hot} from ''react-hot-loader/root'';
import AddItem from ''./component/addItem'';


const store = createStore(reduce)

function AppCont(){
    return <Provider store={store}><AddItem /></Provider>//根组件外面包裹高阶组件
}
const App = hot(() =>{
    return <AppCont />
})

ReactDOM.render(
    <App />,
    document.getElementById(''app'')
)

新建reduce文件,处理reduce函数。由于后面项目庞大后,reduce函都写在一起肯定太臃肿,redux提供了拆分的方法,可以通过业务进行拆分,最后通过combineReducers来合并。

import {combineReducers} from ''redux'';
const initState = {
    addList:[0,0,0,0]
}

function addSum(state=initState,action){
    switch (action.type){
        case ''add'' : 
            return {
                ...state,
                addList:[...action.payload]
            }
        default : return {...state}
    }
    
}
export default combineReducers({
    addSum
})

addItem文件需要做一些修改,将原来写在state的数据,通过store来存取。修改方法跟下面新增加Sum组件一样:通过connect函数来连接组件和store。

import React from ''react'';
import {connect} from ''react-redux'';
function Sum(props){
    const {addList} = props
    return <div>{
        Number(addList[0]) + Number(addList[1]) + Number(addList[2]) + Number(addList[3])
    
    }</div>
}
export default connect(data=>data.addSum)(Sum);

四、引入react-router

react单页面模式下,路由显得很重要了,react将router单独剔除,形成一个单独的组件,使用起来更加的方便,代码的耦合程度也降低不少。
提供HashRouter、BrowserRouter两种根路由方式,重要的区别是HashRouter是通过hash路径访问,在浏览器直接输入地址可以访问,BrowserRouter通过h5的history api访问,输入地址时会像服务器查询,因为是单页面,没有历史所以查询不到,不过通过我们使用webpack的devserver配置historyApiFallback: true,可以访问,服务端渲染也可以访问。

    npm i react-router-dom --save
 <Router>
    <Route path="/" component={AddItem}></Route>
    <Link to="/tab1/1">tab1</Link>
    <Link to="/tab2/2">tab2</Link>
    <Link to="/tab3/3">tab3</Link>
    <Route path="/tab1/:num" component={PageItem}></Route>
    <Route path="/tab2/:num" component={PageItem}></Route>
    <Route path="/tab3/:num" component={PageItem}></Route>
 </Router>

OK,到这里一个简单的react开发框架搭建完毕,看到这里应该入门了吧。

五、引入副作用处理saga

我们说reduce要写成纯函数,写成没有副作用的函数,那么有副作用的事件怎么办?最常见的就是接口的调用。不要慌!redux提供了很多中间件供我们处理副作用函数,也可以自己写,原理就是在dispath(action)前面进行副作用的操作,拿到数据后再进行dispatch。下面引入一个比较好用的处理接口的中间件saga:

给store添加第二个参数:
import { watchIncrementAsync } from ''./saga/index''
const sagaMiddleware = createSagaMiddleware()
const store = createStore(addSum, applyMiddleware(sagaMiddleware));

saga/index.js
import { put, takeEvery,delay } from ''redux-saga/effects''

export function* incrementAsync() {
  console.log(delay)
   yield delay(2000)
   yield put({ type: ''add'',payload:[0,0,0,0] })
}

export function* watchIncrementAsync() {
  yield takeEvery(''INCREMENT_ASYNC'', incrementAsync)
}

需要注意的是,引入redux-saga依赖,因为saga是基于generator语法编写的异步处理方案,所以需要对babel进行配置,npm i @babel/plugin-transform-runtime --save引入runtime依赖。并做以下配置修改:

options:{
    "plugins": ["@babel/plugin-transform-runtime"]
}

对store等文件做了一些修改,完整代码,放到git上了代码地址 。

六、提交git

之前都是先在git端新建项目,然后clone到本地在开发,这次是直接在本地创建的项目,然后在提交的,中间除了一点小问题,就当在这记录一下吧。
1.githab新建仓库,获取地址。

clipboard.png

clipboard.png

2.打开终端,进入项目目录下,git init //初始化

3.git add * //添加所有更改

4.git commit -m "提交备注"

5.git remote add origin https://github.com/wyczmy/rea...
//链接远程仓库

6.git pull --rebase origin master //远程如果创建时有README.md文件,需要执行

7.git push -u origin master //推送到远程 -f 强制推送


如有不妥,欢迎指正!

React项目搭建webpack+TS

React项目搭建webpack+TS

如何快速学会项目搭建

答: 别想了,想啥呢?
只能是遇到问题去搜相关的 issues、文档和讨论(英文)。

步骤

  1. 创建目录与远程仓库
  2. npm 初始化 (npm init)
  3. 新建 lib/index.tsx
  4. 新建 webpack.config.js

    1. 配置 entry
    2. 配置 output
    3. 配置 module.rules
    4. jsx
    5. tsx
    6. scss
    7. 配置 plugins
const path = require(''path'')
module.exports = {
    mode: ''production'',
    // 程序的入口在哪里
    entry: {
        index: ''./lib/index.tsx''
    },
    // 配置出口
    output: {
        // 出口路径
        path: path.resolve(__dirname, ''dist''),
        // 库的名字FUI
        library: ''FUI'',
        // 库的格式
        // umd兼容了三种定义方式,commonJS define script
        // 但是并没有多NB,只是做了if else
        libraryTarget: ''umd'',
    },
    // 编译tsx
    module: {
        rules: [{
            test: /\.tsx?$/,
            // 如果用了ts文件,使用下面loader翻译成js
            loader: ''awesome-typescript-loader''
        }]
    }
}

5.配置 tsconfig.json 和 tslint.json
6.配置 webpack-dev-server 与 webpack.config.dev.js配置 webpack-dev-server 与 webpack.config.dev.js
7.创建 index.html
8.配置 webpack.config.prod.js

const HtmlWebpackPlugin = require(''html-webpack-plugin'');
module.exports = {
    // 前面的配置不变
    ...,
    // 自动找到index.html文件并显示它
    plugins: [
        new HtmlWebpackPlugin({
            template: ''index.html''
        })
    ]
}

9.配置package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "webpack-dev-server",
    "build": "webpack"
  },

10.安装react react-dom运行之后报错
image.png
11.安装@types/react @types/react-dom
yarn add @types/react --dev
yarn add @types/react-dom --dev

yarn.lock这个文件是对你安装东西的版本进行锁,比如你安装的react版本是16.8.4,锁的版本可能就是16.8.5,^这个箭头的意思是,可以高于^后面的版本

12.运行后发现还是报错
image.png

webpack的配置有问题总是说找不见对应文件,这时候配置
// 支持多种文件
resolve: {
    extensions: [''.ts'', ''.tsx'', ''.js'', ''.jsx'']
},

13.不会报错了,但是会处警告,警告也要消除掉
image.png
14.webpack配置里面更改如下,警告取消(同时index.js会变大1.2M)
image.png
15.webpack配置,减小我们打包后的代码体积

// 告诉webpack下面的库是外部的库
externals: {
    react: {
        // 对应不同的打包工具
        commonjs: ''react'',
        commonjs2: ''react'',
        amd: ''react'',
        root: ''React'',
    },
    ''react-dom'': {
        // 对应不同的打包工具
        commonjs: ''react-dom'',
        commonjs2: ''react-dom'',
        amd: ''react-dom'',
        root: ''ReactDOM'',
    },
}

16.为了满足开发和生产的模式,将webpack文件夹分出webpack.config.dev.jsimage.png和webpack.config.prod.jsimage.png
webpack.config.js
image.png
package.json文件改变

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

17.配置index.d.ts到dist目录下
image.png
在tsconfig.json文件中加入"outDir": "dist",
image.png

配置测试

使用 react jest进行测试

  1. 安装:
    yarn add --dev jest babel-jest @babel/preset-env @babel/preset-react react-test-renderer
  2. 配置.babelrc(创建这个文件)
    {

    "presets": [
        "react-app"
    ]

    }

  3. package.json里面配置测试命令
    "test": "cross-env NODE_ENV=test jest --config=jest.config.js --runInBand",
  4. 配置jest.config.js
    // https://jestjs.io/docs/en/con...
    module.exports = {

    verbose: true,
    clearMocks: false,
    collectCoverage: false,
    reporters: ["default"],
    moduleFileExtensions: [''js'', ''jsx'', ''ts'', ''tsx''],
    moduleDirectories: [''node_modules''],
    globals: {
        ''ts-jest'': {
            tsConfig: ''tsconfig.test.json'',
        },
    },
    moduleNameMapper: {
        "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/file-mock.js",
    },
    testMatch: [''<rootDir>/**/__tests__/**/*.unit.(js|jsx|ts|tsx)''],
    transform: {
        "^.+unit\\.(js|jsx)$": "babel-jest",
        ''^.+\\.(ts|tsx)$'': ''ts-jest'',
    },
    setupFilesAfterEnv: ["<rootDir>test/setupTests.js"]

    }

5.安装支持ts的jest

    yarn add --dev ts-jest

6.执行yarn test报错(这个报错是说你没有写测试用例 -- 已经成功了)
image.png

7.安装连个插件

yarn add @types/jest --dev
yarn add @types/react-test-renderer --dev

8.在lib文件夹下面创建__tests__文件夹
image.png
9.神奇的import BUG 在不写* as 的时候会报错
image.png
更改配置(上面注释掉的已经被弃用了),改了之后就不需要写* as啦
image.png

总结

  1. 在配置的过程中会遇到许多问题,报错和警告是绝对不许有得
  2. 遇到问题,首先想办法解决,不要放弃,坚持解决了问题才能提升
  3. https://github.com/sunkuangdo... (配置源码)

我们今天的关于基于 Webpack 4 和 React hooks 搭建项目webpack配置react的分享就到这里,谢谢您的阅读,如果想了解更多关于ES6+React+Webpack初步构建项目流程、React-APP结合webpack搭建项目、React入门:从项目搭建(webpack)到引入路由(react-router)和状态管理(react-redux/saga)、React项目搭建webpack+TS的相关信息,可以在本站进行搜索。

本文标签: