如何使用typescript web和Webpack编写网页应用

Vue+Webpack开发可复用的单页面富应用教程(组件篇) - 简书
<div class="fixed-btn note-fixed-download" data-toggle="popover" data-placement="left" data-html="true" data-trigger="hover" data-content=''>
写了10575字,被23人关注,获得了7个喜欢
Vue+Webpack开发可复用的单页面富应用教程(组件篇)
本文首发于,一个有逼格的程序员社区。转载请注明出处和作者。写在前面本文为系列文章,总共分四节,建议按顺序阅读:在上一节中,我们介绍了在项目中关于webpack的一些基础配置,包括开发环境和生产环境,在本节中,我们重点介绍使用Vue.js和vue-router,通过组件化的方式来开发单页面富应用的相关内容。读者可以clone或下载这个项目,结合具体代码来看本文。基础知识扫盲本段主要介绍一些前端的基础概念,老司机可以直接跳过。单页面富应用(SPA)和前端路由单页面富应用(即Single Page Web Application,以下简称SPA)应该是最近几年火起来的,尤其是在Angular框架诞生后,很多SPA的网站以及基于或的桌面App和移动App层出不穷,比如。SPA的核心即是前端路由。何为路由呢?说的通俗点就是网址,比如;专业点就是每次GET或者POST等请求,在服务端有一个专门的正则配置列表,然后匹配到具体的一条路径后,分发到不同的Controller,然后进行各种操作后,最终将html或数据返回给前端,这就完成了一次IO。当然,目前绝大多数的网站都是这种后端路由,也就是多页面的,这样的好处有很多,比如页面可以在服务端渲染好直接返回给浏览器,不用等待前端加载任何js和css就可以直接显示网页内容,再比如对SEO的友好等。那SPA的缺点也是很明显的,就是模板是由后端来维护或改写。前端开发者需要安装整套的后端服务,必要还得学习像PHP或Java这些非前端语言来改写html结构,所以html和数据、逻辑混为一谈,维护起来即臃肿也麻烦。然后就有了前后端分离的开发模式,后端只提供API来返回数据,前端通过Ajax获取到数据后,再用一定的方式渲染到页面里,这么做的优点就是前后端做的事情分的很清楚,后端专注在数据上,前端专注在交互和可视化上,从此前后搭配,干活不累,如果今后再开发移动App,那就正好能使用一套API了,当然缺点也很明显,就是首屏渲染需要时间来加载css和js。这种开发模式被很多公司认同,也出现了很多前端技术栈,比如以jQuery+artTemplate+Seajs(requirejs)+gulp为主的开发模式所谓是万金油了。在Node.js出现后,这种现象有了改善,就是所谓的大前端,得益于Node.js和JavaScript的语言特性,html模板可以完全由前端来控制,同步或异步渲染完全由前端自由决定,并且由前端维护一套模板,这就是为什么在服务端使用artTemplate、React以及即将推出的Vue2.0原因了。那说了这么多,到底怎样算是SPA呢,其实就是在前后端分离的基础上,加一层前端路由。前端路由,即由前端来维护一个路由规则。实现有两种,一种是利用url的hash,就是常说的锚点(#),JS通过hashChange事件来监听url的改变,IE7及以下需要用轮询;另一种就是HTML5的History模式,它使url看起来像普通网站那样,以"/"分割,没有#,但页面并没有跳转,不过使用这种模式需要服务端支持,服务端在接收到所有的请求后,都指向同一个html文件,不然会出现404。所以,SPA只有一个html,整个网站所有的内容都在这一个html里,通过js来处理。前端路由的优点有很多,比如页面持久性,像大部分音乐网站,你都可以在播放歌曲的同时,跳转到别的页面而音乐没有中断,再比如前后端彻底分离。前端路由的框架,通用的有,更多还是结合具体框架来用,比如Angular的ngRouter,React的ReactRouter,以及我们后面用到的Vue的vue-router。这也带来了新的开发模式:MVC和MVVM。如今前端也可以MVC了,这也是为什么那么多搞Java的钟爱于Angular。开发一个前端路由,主要考虑到页面的可插拔、页面的生命周期、内存管理等。编写可复用的代码、模块化、组件编写可复用的代码是对编程质量的一个体现。写一个通用工具函数、维护一个对象,这些都可以说是可复用的,不过我们这里讨论的,主要是利用CommonJS规范来进行模块化开发。那代码复用和模块化有什么关系呢,其实模块化的一个原因就是可以使代码复用,你开发的模块可以提供给其他人用,一个模块可以是小到一个配置文件,也可以大到一个日历组件。把一个页面拆分成不同的模块,然后来组装,这样既能提高开发效率,又方便维护。那组件又是什么呢?如果说模块化是一种开发模式,那组件就是这种模式的具体实现。比如一个Button按钮、一个输入框,或者一个上传控件都可以封装为一个组件,在使用的时候,可能只用写一行,就能实现文件上传功能,甚至可以支持拖拽上传、大小和格式限制等。那一个组件具体怎么开发呢,这就是本文后面重点讨论的内容了。Vue的路由和它的组件化在项目中,我们使用的技术栈是vue.js+vue-router+webpack,其中webpack的作用已经在中详细介绍了。在说vue-router之前,我们先聊聊Vue的组件。组件的构造可以说是Vue中最神奇也是最难懂的部分了,这部分懂了,vue也就懂了。vue组件的特点是可插拔、独立作用域、观察者模式、完整的生命周期。我们来看一个组件的基本构成:<ponent('child', {
props: ['msg'],
template:'{{ msg }}',
data:function(){return{
title:'TalkingCoder'}
methods: {// ...},
ready:function(){
beforeDestroy:function(){
events: {// ...}});一个组件基本跟一个vue实例是类似的,也有自己的methods和data,只不过data是通过一个function来返回了一个对象,具体原因可以查看vue的文档。props是从父级通过html特性传递来的数据,它可以是字符串、数字、布尔、数组、对象,默认是单向的,也可以设置为双向绑定的。props里的参数可以直接通过像this.msg这种方式调用,这与data的里的数据是一样的。template是这个组件使用的html片段,可以直接是字符串,也可以像'#child'这样标识一个dom节点。ready和beforeDestroy是两个常用的生命周期,ready是在组件准备好时的一个回调,一般在这里我们可以使用获取数据、实例化第三方组件、绑定事件等,beforeDestroy正好相反,是在组件即将被销毁时触发回调,在这里我们销毁自定义的实例、解绑自定义事件、定时器等。如何使用组件组件一般是由它的父级来显示调用的,比如上面的child组件,我们就可以在父级中使用:newVue({
msg1:'Hello,TalkingCoder',
msg2:'你好,TalkingCoder'}})上例使用了两次child组件,使用props传递了一个参数msg,并且第二个组件的参数是双向绑定的,在双向绑定后,无论修改父级还是子元素的msg,双方的数据和view都会响应到,而单向绑定后,子组件修改是不会影响到父级的。在渲染完,的内容就会替换为组件template内的字符串了,虽然使用的是同一个child组件,但是两次使用的作用域是独立的,这也是为什么在组件内data要使用function来返回一个对象的原因。父子组件间的通信在Vue.js中,父子之间的通信主要通过事件来完成,这种就是我们熟悉的观察者模式(或叫订阅-发布模式),很多框架也都使用了这种设计模式,比如Angular。父组件通过Vue内置的$broadcast()向下广播事件和传递数据,子组件通过$dispatch()向上派发事件和传递数据,双方都可以在events对象内接收自定义事件,并且处理各自的业务逻辑。父组件使用了多个相同子组件,如何区分呢?比如我们上面的demo使用了两次child组件,但是如何来区分这两个呢,也就是说如果给child广播事件,如何给其中指定的一个广播呢,因为广播后,它俩都会接收到事件的。我们可以使用v-ref来标识组件:newVue({
msg1:'Hello,TalkingCoder',
msg2:'你好,TalkingCoder'},
methods: {
sendData:function(){this.$refs.child1.$emit('set-data', {});this.$refs.child2.$emit('set-data', {});
}})通过$refs就可以给指定的组件触发事件了,事实上,通过$refs是可以获取到子组件的整个实例的。子组件派发事件,而父组件仍然使用了多个相同子组件,如何区分是哪个组件派发的呢?还是上面的demo,比如我们的child组件$dispatch了一个自定义事件,可以这样来区分:newVue({
msg1:'Hello,TalkingCoder',
msg2:'你好,TalkingCoder'},
methods: {
sendData:function(){this.$refs.child1.$emit('set-data', {});this.$refs.child2.$emit('set-data', {});
handler1:function(){// ...},
handler2:function(){// ...}
}})像绑定DOM2事件一样,使用@xxx或v-bind:xxx来绑定自定义事件,来执行不同的方法。内容分发slot有时候我们编写一个可复用的组件时,比如下面的一个confirm确认框:标题、关闭按钮是统一的,但是中间正文的内容(包括样式)是想自定义的,这时候就会用到Vue组件的slot来分发内容。比如子组件的template的内容为:
提示确定取消父组件这样调用子组件:欢迎来到TalkingCoder最终渲染完的内容为:
欢迎来到TalkingCoder确定取消编写可复用组件这里引用一段来自的内容:在编写组件时,记住是否要复用组件有好处。一次性组件跟其它组件紧密耦合没关系,但是可复用组件应当定义一个清晰的公开接口。Vue.js 组件 API 来自三部分——prop,事件和 slot:prop允许外部环境传递数据给组件;事件允许组件触发外部环境的 action;slot允许外部环境插入内容到组件的视图结构内。使用v-bind和v-on的简写语法,模板的缩进清楚且简洁:Hello!路由、组件和组件化上文说了那么多,现在终于到重点了。在中,我们简单的提到了组件化,这也是将Vue使用到极致的必经之路。我们先看一下src/main.js文件。Vue有点像Express的用法,也有中间件的概念,比如我们用到的vue-router,还有vuex,它们都是vue的中间件,当然我们自己也可以开发基于vue的中间件。importVuefrom'vue';importVueRouterfrom'vue-router';importAppfrom'components/app.vue';importEnvfrom'./config/env';Vue.use(VueRouter);// 开启debug模式Vue.config.debug =// 路由配置varrouter =newVueRouter({
history: Env !='production'});router.map({'/index': {
name:'index',
component:function(resolve){require(['./routers/index.vue'], resolve);
}});router.beforeEach(function(){window.scrollTo(0,0);});router.afterEach(function(transition){});router.redirect({'*':"/index"});router.start(App,'#app');以上代码就是main.js的内容,这也是我们项目跑起来后第一个执行的js文件。在导入了Vue和VueRouter模块后,使用Vue.use(VueRouter)安装路由模块。路由可以做一些全局配置,具体可以,这里只说一个就是history,上文已经介绍了关于HTML5的History,它用history.pushState()和history.replaceState()来管理历史记录,服务器需要正确的配置,否则可能会404。开启后地址栏会像一般网站那样使用“/”来分割,比“#”要优雅很多,可以看到我们通过环境模块env.js默认给开发环境开启了History模式路由,生产环境没有开启,为的是可以让大家来体验到这两者的差异性,使用者可以自己来修改配置。导入的app.vue模块就是我们的入口组件了,上篇文章已经介绍过,我们通过webpack生成的index.html里,body内只有一个挂载节点,当我们通过执行router.start(App, '#app')后,app.vue组件就会挂载到#app内了,所以app.vue组件也是我们工程起来后,第一个被调用的组件,可以在它里面完成一些全局性的操作,比如获取登录信息啊,统计日活啊等等。在app.vue内,有一个的自定义组件,它就是整个网站的路由挂载节点了,切换路由时,它的内容会动态的切换,其实是在动态的切换不同的组件,得益于webpack,路由间的切换可以是异步按需加载。router.map()就是设置路由匹配规则,比如访问127.0.0.1:8080/index,就会匹配到"/index",然后通过component,在回调里使用require()异步加载组件,这个过程是可以改为同步的,不过应该没有人会这么做。vue-router支持匹配带参数的路由,比如'/user/:id'可以匹配到'/user/123',或者'/user/*any/bar'可以匹配到'/user/a/b/bar',:id是参数,*any是全匹配,不过vue-router支持的路由规则还是比较弱的,一般后端框架,比如Python的Tornado或者Node.js的Express是支持正则的。vue的路由只是动态的调用组件,根本上还是MVVM,而Angular的路由是MVC的,在ng的controller里,可以使用templateURL来使用一个html片段,而vue的组件是不支持这种模式的,必须把html字符串写(或编译)在template里,因为在Vue的设计里,一个组件(.vue文件)是应该把它的样式、html和js紧耦合的,这正是组件化的魅力所在。嵌套路由。vue-router是支持嵌套路由的,在app.vue里的是我们的根路由挂载,如果需要,可以在某个具体的路由组件里面再使用一个来分发二级路由。具体使用方法可。路径跳转。vue-router使用v-link指令来跳转,它会隐式的在DOM上绑定点击事件:首页首页如果是在js里跳转,可以这样:module.exports = {
data:function(){return{
methods: {
go:function(){console.log(this.$route);console.log(this.$router);this.$router.go('/index');
}}使用vue内置的$router方法也可以跳转,如果感兴趣,可以试试上面$route和$router打印出什么内容,通过$route是可以得到当前路由的一些状态信息的,比如路径和参数。vue-router还有一些钩子函数,通俗讲就是在发生一次路由时某个状态的一些回调。我们的项目main.js中使用了:router.beforeEach(function(){window.scrollTo(0,0);});router.afterEach(function(transition){console.log(transition);});beforeEach()是在路由切换开始时调用,这里我们将页面返回了顶端。afterEach()是在路由成功切换到激活状态时调用,可以打印出transition看看里面都有什么。一般在这里可以做像自动导航、自动面包屑的一些全局工作。router.redirect()很简单,就是重定向了,找不到路由时可以跳转到指定的路由。小结跟vue相关的组件化内容大概就是这么多了,说到底,vue的路由也是个组件,与普通组件并没有任何差异化,只是概念的不同。vue还有一些知识,比如自定义指令,自定义过滤器,这些原理也很类似,使用也很简单,大家可以参考项目中的demo,结合文档来学习使用。在中,将介绍一些开发中沉淀的技巧或使用经验。
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮
被以下专题收入,发现更多相似内容:
vue.js爱好者集结地,讨论、分享、问答、收录、搜集关于vue的技术问题
· 215人关注
· 0人关注
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
选择支付方式:Webpack 入门指南 - 3. Hello, Angular2! - 冠军 - 博客园
随笔 - 308
评论 - 1925
这一次,我们使用 Webpack 来打包 Angular 2 的应用。
与官方的 Hello, Angular 2 项目相比,我们不使用 System.js,而是使用 TypeScript 直接编译,然后使用 Webpack 打包生成代码。
1. 下载 Angular 2 以及依赖包
修改我们的 package.json 文件,添加 Angular 2 涉及的包,和 Angular 2 所依赖的包,以后,我们可以慢慢介绍各个包的用途,这个文件也可以保存起来,以后直接使用。
"name": "w1",
"version": "1.0.0",
"description": "",
"main": "index.js",
"dependencies": {
"@angular/common": "~2.1.1",
"@angular/compiler": "~2.1.1",
"@angular/core": "~2.1.1",
"@angular/forms": "~2.1.1",
"@angular/http": "~2.1.1",
"@angular/platform-browser": "~2.1.1",
"@angular/platform-browser-dynamic": "~2.1.1",
"@angular/router": "~3.1.1",
"angular-in-memory-web-api": "~0.1.13",
"core-js": "^2.4.1",
"reflect-metadata": "^0.1.8",
"rxjs": "5.0.0-beta.12",
"zone.js": "^0.6.26",
"ie-shim": "^0.1.0"
"devDependencies": {
"html-webpack-plugin": "^2.24.0",
"ts-loader": "^0.9.5",
"typescript": "^2.0.3",
"webpack": "^1.13.2"
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"keywords": [],
"author": "",
"license": "ISC"
黄色是新添加的包,我们可以自己编辑一个应用寄宿的网页,所以,这里将自动生成网页的插件删掉了。
打开控制台窗口,执行 npm install 将这些包安装到你的本地。
2. 修改 tsconfig.json
为 TypeScript 的配置文件 tsconfig.json 再添加两行,以便支持 decorator,这是一个 JavaScript 的新特性,Angular 2 到处使用这个特性。修改之后的文件内容如下。
"compilerOptions": {
"target": "es5",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true
"exclude": [
"node_modules"
3. 创建应用寄宿的网页
这次,我们直接编辑寄宿的网页,而不是自动生成。这是因为我们希望网页中能够添加一段为 Angular 2 应用演出的舞台。
&!DOCTYPE html&
&meta charset=UTF-8&
&meta name="viewport" content="width=device-width, initial-scale=1"&
&title&Hello, Angular2&/title&
Loading...
&script src="./bundle.js"&&/script&
就是 my-app 标记的一段,在 Angular 2 中,我们可以自定义标记了,这个 my-app 就是我们应用以后表演的舞台了。
脚本的一段更加简单,就是直接引用我们将要生成的脚本。
4. 创建第一个 Angular 2 组件
在 Angular 2 中,UI 的基本单位称为组件 Component,一个组件映射到一个自定义的标记上,我们可以自己来定义这个标记实际的内容。
在项目的根目录下创建名为 AppComponent.ts 的文件,内容如下。
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: '&h1&My First Angular App&/h1&'
export class AppComponent { }
@Component 就是声明说我们要定义一个组件了,selector 定义了以后我们如何来使用这个组件,我们在网页中使用的 my-app 标记就来自这里。template 就是在运行的时候,这个 my-app 实际上显示为这个模板的内容。这里就是一个一号的大标题。
5. 定义 Module
Angular 2 定义个一个模块的概念,相关的组件可以封装为一个组件,概念我们以后慢慢学,这里先来一个看看。
在项目根目录下,创建名为 app.module.ts 的文件,内容如下。
import { NgModule }
from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }
from './AppComponent&';
@NgModule({
imports: [ BrowserModule ],
declarations: [ AppComponent ],
bootstrap: [ AppComponent ]
export class AppModule { }
它就是将刚刚定义的 AppComponent 封装到一个 Module 中,注意其中的 bootstrap,是说通过这个组件启动的化,会从 AppComponent 开始。可以理解为第一个界面。
6. 引导应用
要想启动应用,Angular 2 提供了加载器。创建名为 app.ts 的文件,作为我们应用的启动文件。这里使用 Angular 2 提供的浏览器加载器来加载我们的启动模块。
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
platformBrowserDynamic().bootstrapModule( AppModule );
其实程序的基本结构就已经创建了。
但是,还有一个基本的文件需要解决。
在 Angular 2 中,底层依赖了许多基础技术,比如说 ES6 的许多技术,这些当前的 JavaScript 都不支持的,Angular 2 使用 core-js 提供了支持,另外还有 zone.js 等等库的支持。为了提前加载这些依赖库,我们需要创建一个名为 polyfills.browser.ts 的文件来加载这些库,这个文件一般都不需要经常修改。
// Polyfills
import 'ie-shim'; // Internet Explorer 9 support
// import 'core-js/es6';
// Added parts of es6 which are necessary for your project or your browser support requirements.
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/set';
import 'core-js/es6/weak-map';
import 'core-js/es6/weak-set';
import 'core-js/es6/typed';
import 'core-js/es6/reflect';
// see issue /AngularClass/angular2-webpack-starter/issues/709
// import 'core-js/es6/promise';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/long-stack-trace-zone';
最后,我们希望 Webpack 先引入这个 polyfills,然后引导 Angular 2 应用,所以,我们可以再创建一个 index.ts 来完成这个任务。内容非常简单。
import "./polyfills.browser";
import "./app";
7. 使用 Webpack 打包
最后,我们只需要告诉 webpack 从这个 index.ts 开始进行打包就可以了,webpack 可以根据这个文件中 import 导入的模块来找到其它相关的模块,直到找到所有的模块,然后进行编译,打包,最后输出到 bundle.js 就可以了。这次没有使用自动生成网页,实际上,文件更短了,
var HtmlwebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: "./index.ts",
// 输出的文件名
filename: 'bundle.js'
resolve: {
// Add `.ts` and `.tsx` as a resolvable extension.
extensions: ['', '.ts', '.js']
loaders: [
// all files with a `.ts` extension will be handled by `ts-loader`
{ test: /\.ts$/, loader: 'ts-loader' }
8. 打包和运行
如果你已经完成了前面的步骤,打开控制台创建,直接执行 webpack 命令,就会自动生成一个新的 bundle.js 文件了。
启动你的浏览器,直接打开 index.html 网页,应该就可以看到你的第一个 Angular 2 界面了。轻松入门React和Webpack - 推酷
轻松入门React和Webpack
最近在学习React.js,之前都是直接用最原生的方式去写React代码,发现组织起来特别麻烦,之前听人说用Webpack组织React组件得心应手,就花了点时间学习了一下,收获颇丰
一个组件,有自己的结构,有自己的逻辑,有自己的样式,会依赖一些资源,会依赖某些其他组件。比如日常写一个组件,比较常规的方式:
- 通过前端模板引擎定义结构
- JS文件中写自己的逻辑
- CSS中写组件的样式
- 通过RequireJS、SeaJS这样的库来解决模块之间的相互依赖,
那么在React中是什么样子呢?
结构和逻辑
在React的世界里,结构和逻辑交由JSX文件组织,React将模板内嵌到逻辑内部,实现了一个JS代码和HTML混合的JSX。
在JSX文件中,可以直接通过 React.createClass 来定义组件:
var CustomComponent = React.creatClass({
render: function(){
return ( className=&custom-component&&&);
通过这种方式可以很方便的定义一个组件,组件的结构定义在render函数中,但这并不是简单的模板引擎,我们可以通过js方便、直观的操控组件结构,比如我想给组件增加几个节点:
var CustomComponent = React.creatClass({
render: function(){
var $nodes = ['h','e','l','l','o'].map(function(str){
return (&{str}&);
return ( className=&custom-component&&{$nodes}&);
通过这种方式,React使得组件拥有灵活的结构。那么React又是如何处理逻辑的呢?
写过前端组件的人都知道,组件通常首先需要相应自身DOM事件,做一些处理。必要时候还需要暴露一些外部接口,那么React组件要怎么做到这两点呢?
比如我有个按钮组件,点击之后需要做一些处理逻辑,那么React组件大致上长这样:
var ButtonComponent = React.createClass({
render: function(){
return (&屠龙宝刀,点击就送&);
点击按钮应当触发相应地逻辑,一种比较直观的方式就是给button绑定一个 onclick 事件,里面就是需要执行的逻辑了:
function getDragonKillingSword() {
var ButtonComponent = React.createClass({
render: function(){
return ( onclick=&getDragonKillingSword()&&屠龙宝刀,点击就送&);
但事实上 getDragonKillingSword() 的逻辑属于组件内部行为,显然应当包装在组件内部,于是在React中就可以这么写:
var ButtonComponent = React.createClass({
getDragonKillingSword: function(){
render: function(){
return ( onClick={this.getDragonKillingSword}&屠龙宝刀,点击就送&);
这样就实现内部事件的响应了,那如果需要暴露接口怎么办呢?
事实上现在 getDragonKillingSword 已经是一个接口了,如果有一个父组件,想要调用这个接口怎么办呢?
父组件大概长这样:
var ImDaddyComponent = React.createClass({
render: function(){
//其他组件
//其他组件
那么如果想手动调用组件的方法,首先在ButtonComponent上设置一个 ref=&& 属性来标记一下,比如这里把子组件设置成 &ButtonComponent ref=&getSwordButton&/& ,那么在父组件的逻辑里,就可以在父组件自己的方法中通过这种方式来调用接口方法:
this.refs.getSwordButton.getDragonKillingSword();
看起来屌屌哒~那么问题又来了,父组件希望自己能够按钮点击时调用的方法,那该怎么办呢?
父组件可以直接将需要执行的函数传递给子组件:
&ButtonComponent clickCallback={this.getSwordButtonClickCallback}/&
然后在子组件中调用父组件方法:
var ButtonComponent = React.createClass({
render: function(){
return ( onClick={this.props.clickCallback}&屠龙宝刀,点击就送&);
子组件通过 this.props 能够获取在父组件创建子组件时传入的任何参数,因此 this.props 也常被当做配置参数来使用
屠龙宝刀每个人只能领取一把,按钮点击一下就应该灰掉,应当在子组件中增加一个是否点击过的状态,这又应当处理呢?
在React中,每个组件都有自己的状态,可以在自身的方法中通过 this.state 取到,而初始状态则通过 getInitialState() 方法来定义,比如这个屠龙宝刀按钮组件,它的初始状态应该是没有点击过,所以 getInitialState 方法里面应当定义初始状态 clicked: false 。而在点击执行的方法中,应当修改这个状态值为 click: true :
var ButtonComponent = React.createClass({
getInitialState: function(){
clicked: false
getDragonKillingSword: function(){
this.setState({
clicked: true
render: function(){
return ( onClick={this.getDragonKillingSword}&屠龙宝刀,点击就送&);
这样点击状态的维护就完成了,那么render函数中也应当根据状态来维护节点的样式,比如这里将按钮设置为 disabled ,那么render函数就要添加相应的判断逻辑:
render: function(){
var clicked = this.state.
if(clicked)
return ( disabled=&disabled& onClick={this.getDragonKillingSword}&屠龙宝刀,点击就送&);
return ( onClick={this.getDragonKillingSword}&屠龙宝刀,点击就送&);
这里简单介绍了通过JSX来管理组件的结构和逻辑,事实上React给组件还定义了很多方法,以及组件自身的生命周期,这些都使得组件的逻辑处理更加强大
CSS文件定义了组件的样式,现在的模块加载器通常都能够加载CSS文件,如果不能一般也提供了相应的插件。事实上CSS、图片可以看做是一种资源,因为加载过来后一般不需要做什么处理。
React对这一方面并没有做特别的处理,虽然它提供了Inline Style的方式把CSS写在JSX里面,但估计没有多少人会去尝试,毕竟现在CSS样式已经不再只是简单的CSS文件了,通常都会去用Less、Sass等预处理,然后再用像postcss、myth、autoprefixer、cssmin等等后处理。资源加载一般也就简单粗暴地使用模块加载器完成了
组件依赖的处理一般分为两个部分:组件加载和组件使用
React没有提供相关的组件加载方法,依旧需要通过 &script& 标签引入,或者使用模块加载器加载组件的JSX和资源文件。
如果细心,就会发现其实之前已经有使用的例子了,要想在一个组件中使用另外一个组件,比如在 ParentComponent 中使用 ChildComponent ,就只需要在 ParentComponent 的 render() 方法中写上 &ChildComponent /& 就行了,必要的时候还可以传些参数。
到这里就会发现一个问题,React除了只处理了结构和逻辑,资源也不管,依赖也不管。是的,React将近两万行代码,连个模块加载器都没有提供,更与Angularjs,jQuery等不同的是,他还不带啥脚手架…没有Ajax库,没有Promise库,要啥啥没有…
那它为啥这么大?因为它实现了一个虚拟DOM(Virtual DOM)。虚拟DOM是干什么的?这就要从浏览器本身讲起
如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。
而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升
道理我都懂,可是为什么我们没有模块加载器?
所以就需要Webpack了
说说Webpack
什么是Webpack?
事实上它是一个打包工具,而不是像RequireJS或SeaJS这样的模块加载器,通过使用Webpack,能够像Node.js一样处理依赖关系,然后解析出模块之间的依赖,将代码打包
安装Webpack
首先得有Node.js
然后通过 npm install -g webpack 安装webpack,当然也可以通过gulp来处理webpack任务,如果使用gulp的话就 npm install --save-dev gulp-webpack
配置Webpack
Webpack的构建过程需要一个配置文件,一个典型的配置文件大概就是这样
var webpack = require('webpack');
var commonsPlugin = new monsChunkPlugin('common.js');
module.exports = {
entry1: './entry/entry1.js',
entry2: './entry/entry2.js'
path: __dirname,
filename: '[name].entry.js'
resolve: {
extensions: ['', '.js', '.jsx']
loaders: [{
test: /\.js$/,
loader: 'babel-loader'
test: /\.jsx$/,
loader: 'babel-loader!jsx-loader?harmony'
plugins: [commonsPlugin]
这里对Webpack的打包行为做了配置,主要分为几个部分:
entry:指定打包的入口文件,每有一个键值对,就是一个入口文件
output:配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称,filename里面的 [name] 会由entry中的键(这里是entry1和entry2)替换
resolve:定义了解析模块路径时的配置,常用的就是extensions,可以用来指定模块的后缀,这样在引入模块时就不需要写后缀了,会自动补全
module:定义了对模块的处理逻辑,这里可以用loaders定义了一系列的加载器,以及一些正则。当需要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的原因。比如这里定义了凡是 .js 结尾的文件都是用 babel-loader 做处理,而 .jsx 结尾的文件会先经过 jsx-loader 处理,然后经过 babel-loader 处理。当然这些loader也需要通过 npm install 安装
plugins: 这里定义了需要使用的插件,比如commonsPlugin在打包多个入口文件时会提取出公用的部分,生成common.js
当然Webpack还有很多其他的配置,具体可以参照它的
如果通过 npm install -g webpack 方式安装webpack的话,可以通过命令行直接执行打包命令,比如这样:
$webpack --config webpack.config.js
这样就会读取当前目录下的webpack.config.js作为配置文件执行打包操作
如果是通过gulp插件gulp-webpack,则可以在gulpfile中写上gulp任务:
var gulp = require('gulp');
var webpack = require('gulp-webpack');
var webpackConfig = require('./webpack.config');
gulp.task(&webpack&, function() {
return gulp
.src('./')
.pipe(webpack(webpackConfig))
.pipe(gulp.dest('./build'));
使用Babel提升逼格
Webpack使得我们可以使用Node.js的CommonJS规范来编写模块,比如一个简单的Hello world模块,就可以这么处理:
var React = require('react');
var HelloWorldComponent = React.createClass({
displayName: 'HelloWorldComponent',
render: function() {
return (&Hello world&);
module.exports = HelloWorldC
等等,这和之前的写法没啥差别啊,依旧没有逼格…程序员敲码要有geek范,要逼格than逼格,这太low了。现在都ES6了,React的代码也要写ES6, babel-loader 就是干这个的。
能够将ES6代码转换成ES5。首先需要通过命令 npm install --save-dev babel-loader 来进行安装,安装完成后就可以使用了,一种使用方式是之前介绍的在 webpack.config.js 的loaders中配置,另一种是直接在代码中使用,比如:
var HelloWorldComponent = require('!babel!jsx!./HelloWorldComponent');
那我们应当如何使用Babel提升代码的逼格呢?改造一下之前的HelloWorld代码吧:
import React from 'react';
export default class HelloWorldComponent ponent {
constructor() {
this.state = {};
render() {
return (&Hello World&);
这样在其他组件中需要引入HelloWorldComponent组件,就只要就可以了:
import HelloWorldComponent from './HelloWorldComponent'
怎么样是不是更有逼格了?通过import引入模块,还可以直接定义类和类的继承关系,这里也不再需要 getInitialState 了,直接在构造函数 constructor 中用 this.state = xxx 就好了
Babel带来的当然还不止这些,在其帮助下还能尝试很多优秀的ES6特性,比如箭头函数,箭头函数的特点就是内部的this和外部保持一致,从此可以和 that 、 _this 说再见了
['H', 'e', 'l', 'l', 'o'].map((c) =& {
return (&{c}&);
其他还有很多,具体可以参照
我是一个强烈地Less依赖患者,脱离了Less直接写CSS就会出现四肢乏力、不想干活、心情烦躁等现象,而且还不喜欢在写Less时候加前缀,平常都是gulp+less+autoprefixer直接处理的,那么在Webpack组织的React组件中要怎么写呢?
没错,依旧是使用loader
可以在 webpack.config.js 的loaders中增加Less的配置:
test: /\.less$/,
loader: 'style-loader!css-loader!autoprefixer-loader!less-loader'
通过这样的配置,就可以直接在模块代码中引入Less样式了:
import React from 'react';
require('./HelloWorldComponent.less');
export default class HelloWorldComponent ponent {
constructor() {
this.state = {};
render() {
return (&Hello World&);
Webpack的loader为React组件化提供了很多帮助,像图片也提供了相关的loader:
{ test: /\.png$/, loader: &url-loader?mimetype=image/png& }
更多地loader可以移步
在Webpack下实时调试React组件
Webpack和React结合的另一个强大的地方就是,在修改了组件源码之后,不刷新页面就能把修改同步到页面上。这里需要用到两个库 webpack-dev-server 和 react-hot-loader 。
首先需要安装这两个库, npm install --save-dev webpack-dev-server react-hot-loader
安装完成后,就要开始配置了,首先需要修改entry配置:
helloworld: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./helloworld'
通过这种方式指定资源热启动对应的服务器,然后需要配置 react-hot-loader 到loaders的配置当中,比如我的所有组件代码全部放在scripts文件夹下:
test: /\.js?$/,
loaders: ['react-hot', 'babel'],
include: [path.join(__dirname, 'scripts')]
最后配置一下plugins,加上热替换的插件和防止报错的插件:
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin()
这样配置就完成了,但是现在要调试需要启动一个服务器,而且之前配置里映射到 http://localhost:3000 ,所以就在本地3000端口起个服务器吧,在项目根目录下面建个server.js:
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
historyApiFallback: true
}).listen(3000, 'localhost', function (err, result) {
if (err) console.log(err);
console.log('Listening at localhost:3000');
这样就可以在本地3000端口开启调试服务器了,比如我的页面是根目录下地 index.html ,就可以直接通过 http://localhost:3000/index.html 访问页面,修改React组件后页面也会被同步修改,这里貌似使用了websocket来同步数据。图是一个简单的效果:
React的组件化开发很有想法,而Webpack使得React组件编写和管理更加方便,这里只涉及到了React和Webpack得很小一部分,还有更多的最佳实践有待在学习的路上不断发掘
已发表评论数()
请填写推刊名
描述不能大于100个字符!
权限设置: 公开
仅自己可见
正文不准确
标题不准确
排版有问题
主题不准确
没有分页内容
图片无法显示
视频无法显示
与原文不一致

我要回帖

更多关于 webpack typescript 的文章

 

随机推荐