Webpack + React 开发之路

杂七杂八的想法

记得大二的时候刚学习 Java,我做的第一个图形化用户界面是一个仿QQ的登录窗口,其实就是一些输入框和按钮,但是记得当时觉得超级有成就感,于是后来开始喜欢上写 Java,还做了很多小游戏像飞机大战、坦克大战啥的,自己还觉得特别有意思。
后来开始学前端,其实想想也是做图形化用户界面,不过是换了一个运行环境而已。但是写着写着发现很不顺手,和用 Java 写感觉很不一样,到底哪不对呢。
用 Java 写界面的时候,按钮是按钮,输入框是输入框,我做登录窗口的时候,只要定义一个登录窗口类,然后设置布局、把按钮、输入框加进去,一个登录窗口就出来了。
反观前端的实现,要写一个登录窗口,得先在 html 里定义结构,在 css 里制定样式,然后在 js 里添加行为,最头疼的是 js 里不仅仅只是这个登录窗口的行为,还有页面初始化的代码、别的按钮的监听等等等等一大堆乱七八糟的代码(作为菜鸟的自我吐槽)
其实我理解的以上问题的关键词就是 组件化 ,之所以以前写的那么别扭,很大程度上是自己带着组件化的思想,但是写不出组件化的代码。

直到现在使用上 React,真是感觉眼前一亮。当然还有很多很多需要学习的地方,就从现在开始,配合着 Webpack,踏上 React 的开发之路吧。

制作一个微博发送表单

下面通过 React 编写一个简单的例子,就是常用的微博发送的表单。

一、新建项目

项目目录如下:

/js
-- /components
---- /Publisher
------ Publish.css
------ Publish.jsx
-- app.js
/css
-- base.css
index.html
webpack.config.js
  • js/components 目录存放所有的组件,比如 Publisher 是我们的表单组件,里面存放这个表单的子组件(如果有的话)、组件的 jsx 文件以及组件自己的样式。

  • js/app.js 是入口文件

  • css 存放全局样式

  • index.html 主页

  • webpack.config.js webpack 的配置文件

二、配置 Webpack

编辑 webpack.config.js

var webpack = require('webpack');

module.exports = {
    entry: './js/app.js',output: {
        path: __dirname,filename: 'bundle.js'
    },module: {
        loaders: [
            {
                test: /\.jsx?$/,loader: 'babel',query: {
                    presets: ['react','es2015']
                }
            },{
                test: /\.css$/,loader: 'style!css'
            }
        ]
    },plugins: [
        new webpack.optimize.UglifyJsPlugin({
            compress: {
                warnings: false
            }
        })
    ]
}

上一篇文章 里是使用 webpack 进行 ES6 开发,其实不管是 ES6 也好,React 也好,webpack 起到的是一个打包器的作用,配置项和这里大致相似,就不再赘述。

不同的是在 babel-loader 里增加了 react 的转码规则。

另外这里使用到了 webpack 的一个内置插件 UglifyJsPlugin,通过他可以对生成的文件进行压缩。详细的介绍请看这里

三、安装一系列小编

首先保证安装了 nodejs 。

1) 初始化项目

npm init

2) 安装 webpack

npm install webpack -g

3) 安装 React

npm install react react-dom --save-dev

4) 安装加载器

本项目使用到的有 babel-loader、css-loader、style-loader。

  • babel-loader 进行转码

  • css-loader 对 css 文件进行打包

  • style-loader 将样式添加进 DOM 中

详细请看这里

npm install babel-loader css-loader style-loader --save-dev

5) 安装转码规则

npm install babel-preset-es2015 babel-preset-react --save-dev

四、码代码

index.html 中,引用的 js 文件是通过 webpack 生成的 bundle.jscss 文件是写在 /css 目录下的 base.css

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <link rel="stylesheet" href="css/base.css">
</head>
<body>
    <div id="container"></div>
    <script src="bundle.js"></script>
</body>
</html>

/css/base.css

base.css 里面存放的是全局样式,也就是与组件无关的。

html,body,textarea {
    padding: 0;
    margin: 0;
}

body {
    font: 12px/1.3 'Arial','Microsoft YaHei';
    background: #73a2b0;
}

textarea {
    resize: none;
}

a {
    color: #368da7;
    text-decoration: none;
}

/js/app.js

/js/app.js 是入口文件,引入了 Publisher 组件

import React from 'react';
import ReactDOM from 'react-dom';
import Publisher from './components/Publisher/Publisher.jsx';

ReactDOM.render(
    <Publisher />,document.getElementById('container')
);

/js/components/Publisher/Publisher.jsx

好的,下面开始编写组件,首先,确定这个组件的组成部分,因为是一个简单的表单,所以不需要继续划分子组件

表单分为上中下三部分,title 里面包含热门微博和剩余字数的提示,textElDiv 包含输入框,btnWrap 包含发布按钮。

import React from 'react';

class Publisher extends React.Component {
    constructor(...args) {
        super(...args);
    }

    render() {
        return (
            <div className="publisher">
                <div className="title">
                    <div>
                        <a href="#">网友曝光两女孩蹲着等地铁,称没教养,你怎么看(投票)</a>
                    </div>
                    <div className="tips">
                        <span>还可以输入</span><strong>140</strong>字
                    </div>
                </div>            
                <div className="textElDiv">
                    <textarea></textarea>
                </div>
                <div className="btnWrap">
                    <a className="publishBtn" href="javascript:void(0)">发布</a>
                </div>
            </div>
        );
    }
}

export default Publisher;

我们暂时通过 className 给组件定义了样式名,但还没有实际写样式代码,因为要保证组件的封装性,所以我们不希望组件的样式编写到全局中去以免影响其他组件,最好像我们的目录划分一样,组件自己的样式跟着组件自己走,而且这个样式不影响其他组件。这里就需要用到 css-loader了。

css-loader 可以将 css 文件进行打包,而且可以对 css 文件里的 局部 className 进行哈希编码。这意味着可以这样写样式文件:

/* xxx.css */

:local(.className) { background: red; }
:local .className { color: green; }
:local(.className .subClass) { color: green; }
:local .className .subClass :global(.global-class-name) { color: blue; }

经过处理之后,则变成:

._23_aKvs-b8bW2Vg3fwHozO { background: red; }
._23_aKvs-b8bW2Vg3fwHozO { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; }
._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; }

也就是我们可以在不同的组件样式中定义 .btn 的样式名,但是经过打包之后,在全局里面就被转成了不同的哈希编码,由此解决了 css 全局命名冲突的问题。

关于 css-loader 更详细的使用,请参考这里

那么 Publisher 的样式如下:

/js/components/Publisher/Publisher.css

:local .publisher{
    width: 600px;
    margin: 10px auto;
    background: #ffffff;
    box-shadow: 0 0 2px rgba(0,0.15);
    border-radius: 2px;
    padding: 15px 10px 10px;
    height: 140px;
    position: relative;
    font-size: 12px;
}

:local .title{
    position: relative;
}

:local .title div {
    position: absolute;
    right: 0;
    top: 2px;
}

:local .tips {
    color: #919191;
    display: none;
}

:local .textElDiv {
    border: 1px #cccccc solid;
    height: 68px;
    margin: 25px 0 0;
    padding: 5px;
    box-shadow: 0px 0px 3px 0px rgba(0,0.15) inset;
}

:local .textElDiv textarea {
    border: none;
    border: 0px;
    font-size: 14px;
    word-wrap: break-word;
    line-height: 18px;
    overflow-y: auto;
    overflow-x: hidden;
    outline: none;
    background: transparent;
    width: 100%;
    height: 68px;
}

:local .btnWrap {
    float: right;
    padding: 5px 0 0;
}

:local .publishBtn {
    display: inline-block;
    height: 28px;
    line-height: 29px;
    width: 60px;
    font-size: 14px;
    background: #ff8140;
    border: 1px solid #f77c3d;
    border-radius: 2px;
    color: #fff;
    box-shadow: 0px 1px 2px rgba(0,0.25);
    padding: 0 10px 0 10px;
    text-align: center;
    outline: none;
}

:local .publishBtn.disabled {
    background: #ffc09f;
    color: #fff;
    border: 1px solid #fbbd9e;
    box-shadow: none;
    cursor: default;
}

然后就可以在 Publisher.jsx 中这样使用了

import React from 'react';
import style from './Publisher.css';

class Publisher extends React.Component {
    constructor(...args) {
        super(...args);
    }

    render() {
        return (
            <div className={style.publisher}>
                <div className={style.title}>
                    <div>
                        <a href="#">网友曝光两女孩蹲着等地铁,你怎么看(投票)</a>
                    </div>
                    <div className={style.tips}>
                        <span>还可以输入</span><strong>140</strong>字
                    </div>
                </div>            
                <div className={style.textElDiv}>
                    <textarea></textarea>
                </div>
                <div className={style.btnWrap}>
                    <a className={style.publishBtn>发布</a>
                </div>
            </div>
        );
    }
}

export default Publisher;

这样组件的样式已经添加进去了,接下来就纯粹是进行 React 开发了。

编写 Publisher.jsx

表单的需求如下:

  1. 输入框获取焦点时,输入框边框变为橙色,右上角显示剩余字数的提示;输入框失去焦点时,输入框边框变为灰色,右上角显示热门微博。

  2. 输入字数小于且等于140字时,提示显示剩余可输入字数;输入字数大于140时,提示显示已经超过字数。

  3. 输入字数大于0且不大于140字时,按钮为亮橙色且可点击,否则为浅橙色且不可点击。

首先,给 textarea 添加 onFocusonBluronChange 事件,通过 handleFocushandleBlurhandleChange 来处理输入框获取焦点、失去焦点和输入。

然后将输入的内容保存在 state 里,这样每当内容发生变化时,就能方便的对变化进行处理。

对于按钮的变化、热门微博和提示之间的转换,根据 state 中内容的变化来切换样式就能轻松地做到。

完整代码如下:

import React from 'react';
import style from './Publisher.css';

class Publisher extends React.Component {
    constructor(...args) {
        super(...args);
        // 定义 state
        this.state = {
            content: ''
        }
    }

    /**
    * 获取焦点
    **/
    handleFocus() {
        // 改变边框颜色
        this.refs.textElDiv.style.borderColor = '#fa7d3c';
        // 切换右上角内容
        this.refs.hot.style.display = 'none';
        this.refs.tips.style.display = 'block';
    }

    /**
    * 失去焦点
    **/
    handleBlur() {
        // 改变边框颜色
        this.refs.textElDiv.style.borderColor = '#cccccc';
        // 切换右上角内容
        this.refs.hot.style.display = 'block';
        this.refs.tips.style.display = 'none';
    }

    /**
    * 输入框内容发生变化
    **/
    handleChange(e) {
        // 改变状态值
        this.setState({
            content: e.target.value
        });
    }

    render() {
        return (
            <div className={style.publisher}>
                <div className={style.title}>
                    <div ref="hot">
                        <a href="#">网友曝光两女孩蹲着等地铁,你怎么看(投票)</a>
                    </div>
                    <div className={style.tips} ref="tips">
                        <span>{this.state.content.length > 140 ? '已超出' : '还可以输入'}</span><strong>{this.state.content.length > 140 ? this.state.content.length - 140 : 140 - this.state.content.length}</strong>字
                    </div>
                </div>            
                <div className={style.textElDiv} ref="textElDiv">
                    <textarea onFocus={this.handleFocus.bind(this)} onBlur={this.handleBlur.bind(this)} onChange={this.handleChange.bind(this)}></textarea>
                </div>
                <div className={style.btnWrap}>
                    <a className={style.publishBtn + ((this.state.content.length > 0 && this.state.content.length <= 140) ? '' : ' ' + style.disabled)} href="javascript:void(0)">发布</a>
                </div>
            </div>
        );
    }
}

export default Publisher;

五、运行

  • 通过 --display-error-detail 可以显示 webpack 出现错误的中间过程,方便在出错时进行查看。

  • --progress --colors 可以显示进度

  • --watch 可以监视文件的变化并在变化后重新加载

运如如下:

webpack --display-error-detail --progress --colors --watch

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


react 中的高阶组件主要是对于 hooks 之前的类组件来说的,如果组件之中有复用的代码,需要重新创建一个父类,父类中存储公共代码,返回子类,同时把公用属性...
我们上一节了解了组件的更新机制,但是只是停留在表层上,例如我们的 setState 函数式同步执行的,我们的事件处理直接绑定在了 dom 元素上,这些都跟 re...
我们上一节了解了 react 的虚拟 dom 的格式,如何把虚拟 dom 转为真实 dom 进行挂载。其实函数是组件和类组件也是在这个基础上包裹了一层,一个是调...
react 本身提供了克隆组件的方法,但是平时开发中可能很少使用,可能是不了解。我公司的项目就没有使用,但是在很多三方库中都有使用。本小节我们来学习下如果使用该...
mobx 是一个简单可扩展的状态管理库,中文官网链接。小编在接触 react 就一直使用 mobx 库,上手简单不复杂。
我们在平常的开发中不可避免的会有很多列表渲染逻辑,在 pc 端可以使用分页进行渲染数限制,在移动端可以使用下拉加载更多。但是对于大量的列表渲染,特别像有实时数据...
本小节开始前,我们先答复下一个同学的问题。上一小节发布后,有小伙伴后台来信问到:‘小编你只讲了类组件中怎么使用 ref,那在函数式组件中怎么使用呢?’。确实我们...
上一小节我们了解了固定高度的滚动列表实现,因为是固定高度所以容器总高度和每个元素的 size、offset 很容易得到,这种场景也适合我们常见的大部分场景,例如...
上一小节我们处理了 setState 的批量更新机制,但是我们有两个遗漏点,一个是源码中的 setState 可以传入函数,同时 setState 可以传入第二...
我们知道 react 进行页面渲染或者刷新的时候,会从根节点到子节点全部执行一遍,即使子组件中没有状态的改变,也会执行。这就造成了性能不必要的浪费。之前我们了解...
在平时工作中的某些场景下,你可能想在整个组件树中传递数据,但却不想手动地通过 props 属性在每一层传递属性,contextAPI 应用而生。
楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试...
我们上一节了了解了函数式组件和类组件的处理方式,本质就是处理基于 babel 处理后的 type 类型,最后还是要处理虚拟 dom。本小节我们学习下组件的更新机...
前面几节我们学习了解了 react 的渲染机制和生命周期,本节我们正式进入基本面试必考的核心地带 -- diff 算法,了解如何优化和复用 dom 操作的,还有...
我们在之前已经学习过 react 生命周期,但是在 16 版本中 will 类的生命周期进行了废除,虽然依然可以用,但是需要加上 UNSAFE 开头,表示是不安...
上一小节我们学习了 react 中类组件的优化方式,对于 hooks 为主流的函数式编程,react 也提供了优化方式 memo 方法,本小节我们来了解下它的用...
开源不易,感谢你的支持,❤ star me if you like concent ^_^
hel-micro,模块联邦sdk化,免构建、热更新、工具链无关的微模块方案 ,欢迎关注与了解
本文主题围绕concent的setup和react的五把钩子来展开,既然提到了setup就离不开composition api这个关键词,准确的说setup是由...
ReactsetState的执行是异步还是同步官方文档是这么说的setState()doesnotalwaysimmediatelyupdatethecomponent.Itmaybatchordefertheupdateuntillater.Thismakesreadingthis.staterightaftercallingsetState()apotentialpitfall.Instead,usecom