杂七杂八的想法
记得大二的时候刚学习 Java,我做的第一个图形化用户界面是一个仿QQ的登录窗口,其实就是一些输入框和按钮,但是记得当时觉得超级有成就感,于是后来开始喜欢上写 Java,还做了很多小游戏像飞机大战、坦克大战啥的,自己还觉得特别有意思。
后来开始学前端,其实想想也是做图形化用户界面,不过是换了一个运行环境而已。但是写着写着发现很不顺手,和用 Java 写感觉很不一样,到底哪不对呢。
用 Java 写界面的时候,按钮是按钮,输入框是输入框,我做登录窗口的时候,只要定义一个登录窗口类,然后设置布局、把按钮、输入框加进去,一个登录窗口就出来了。
反观前端的实现,要写一个登录窗口,得先在 html 里定义结构,在 css 里制定样式,然后在 js 里添加行为,最头疼的是 js 里不仅仅只是这个登录窗口的行为,还有页面初始化的代码、别的按钮的监听等等等等一大堆乱七八糟的代码(作为菜鸟的自我吐槽)
其实我理解的以上问题的关键词就是 组件化 ,之所以以前写的那么别扭,很大程度上是自己带着组件化的思想,但是写不出组件化的代码。
直到现在使用上 React,真是感觉眼前一亮。当然还有很多很多需要学习的地方,就从现在开始,配合着 Webpack,踏上 React 的开发之路吧。
制作一个微博发送表单
下面通过 React 编写一个简单的例子,就是常用的微博发送的表单。
@H_502_20@一、新建项目
项目目录如下:
/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.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。
详细请看这里。
npm install babel-loader css-loader style-loader --save-dev
5) 安装转码规则
npm install babel-preset-es2015 babel-preset-react --save-dev@H_502_20@四、码代码
index.html
中,引用的 js
文件是通过 webpack 生成的 bundle.js
, css
文件是写在 /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
全局命名冲突的问题。
那么 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
表单的需求如下:
输入框获取焦点时,输入框边框变为橙色,右上角显示剩余字数的提示;输入框失去焦点时,输入框边框变为灰色,右上角显示热门微博。
输入字数大于0且不大于140字时,按钮为亮橙色且可点击,否则为浅橙色且不可点击。
首先,给 textarea
添加 onFocus
、onBlur
、onChange
事件,通过 handleFocus
、handleBlur
、handleChange
来处理输入框获取焦点、失去焦点和输入。
然后将输入的内容保存在 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;@H_502_20@五、运行
通过
--display-error-detail
可以显示 webpack 出现错误的中间过程,方便在出错时进行查看。--progress --colors
可以显示进度--watch
可以监视文件的变化并在变化后重新加载
运如如下:
webpack --display-error-detail --progress --colors --watch