接下来的系列文章将回到自己熟悉的mvvm框架——react。
作为《深入浅出react和redux》的读书笔记,文章将重点关注自身未去深入理解的问题。
先用官方脚手架create-react-app
创建项目:
create-react-app aaacd aaanpm start
写一个点击计数器:
import React, { Component } from 'react';
export default class ClickCounter extends Component { constructor(props){ super(props); this.state={ count:0 } }
handleClick=()=>{ this.setState((preState)=>{ return { counter:preState.count++ } }) }
render() { return ( <div> <div>{this.state.count}</div> <button onClick={this.handleClick}>点我</button> </div> ) }}
相信这个很快就能写出来。
接下来做少许分析:
import React, { Component } from 'react'
Component作为所有组件的基类,提供了很多组件共有的功能,下面这行代码,使用ES6语法来创建一个ClickCounter的组件类,ClickCounter的父类就是Component:
export default class ClickCounter extends Component { // ...}
如果去掉导入语句中的React,会发生什么?
代码会立马报错:大致意思是说,所有使用jsx的地方必须引用React。
这里补白一个问题:
为什么行内样式,行内事件处理被人诟病,在react中却成为了一种常用的写法?
首先jsx属于js而非html,,JSX的onClick事件处理方式和HTML的onclick有很大不同。
即使现在,在HTML中直接使用onclick很不专业,原因如下:·
•onclick添加的事件处理函数是在全局环境下执行的,这污染了全局环境,很容易产生意料不到的后果;•给很多DOM元素添加onclick事件,可能会影响网页的性能,毕竟,网页需要的事件处理函数越多,性能就会越低;•·对于使用onclick的DOM元素,如果要动态地从DOM树中删掉的话,需要把对应的事件处理器注销,假如忘了注销,就可能造成内存泄露,这样的bug很难被发现。
——而上面说的这些问题,在JSX中都不存在。
jsx事件特点:
•挂载的事件处理函数,作用域只作用在组件范围内。•onClick使用了事件委托(event delegation)的方式处理点击事件,无论有多少个onClick出现,其实最后都只在DOM树上添加了一个事件处理函数,挂在最顶层的DOM节点上。所有的点击事件都被这个事件处理函数捕获,然后根据具体组件分配给特定函数,使用事件委托的性能当然要比为每个onClick都挂载一个事件处理函数要高。
•因为React控制了组件的生命周期,在unmount的时候自然能够清除相关的所有事件处理函数,内存泄露也不再是一个问题。
前端最喜欢的npm语句应该是npm start
,看下官方脚手架的命令脚本:
"scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" },
react-scripts是官方脚手架提供的一个npm包,我们尝试用npm run eject
(弹射)语句把它封装的工程配置不可逆地暴露出来。
如果你用的是mac机,先执行以下git命令,否则会报错:
git add .git commit -m""
执行后,项目多了若干文件夹,再看package,json:
"scripts": { "start": "node scripts/start.js", "build": "node scripts/build.js", "test": "node scripts/test.js" },
而在config下面则暴露了webpack配置(webpack.config.js):
{ test: /\.(js|mjs|jsx|ts|tsx)$/, include: paths.appSrc, loader: require.resolve('babel-loader'), options: { // ... }, },
paths.appSrc
指向的就是src目录,这段代码表示,所有js|mjs|jsx|ts|tsx后缀的文件,全部由babel-loader处理。
这个年代,说'"以jquery作为开发语言的前端是没前途的",恐怕没有人会反对。
如果用jquery实现一个计数器,可能是这样的:
$('#btn').click(function(){ var count = parseInt($('#show').text()) $('#show').text(count+1)})
在jQuery的解决方案中,首先根据CSS规则找到id为btn的按钮,挂上一个匿名事件处理函数,在事件处理函数中,选中那个需要被修改的DOM元素,读取其中的文本值,加以修改,然后修改这个DOM元素——选中一些DOM元素,然后对这些元素做一些操作,这是一种最容易理解的开发模式。
假设你用jquery维护一个含有表单的模态框,你得给你的对象做好重置表单,打开,关闭,获取表单参数的事件,最后维护的精力是相当恶心的。
与jQuery不同,用React开发应用是另一种体验,用React开发的ClickCounter组件并没有像jQuery那样做“选中一些DOM元素然后做一些事情”的动作。
React的工作方式是,开发者只需要着重“我想要显示什么”,而不用操心怎样去做。这种思维方式,对于一个简单的例子也要编写不少代码,但是对于一个大型的项目,这种方式编写的代码会更容易管理,因为整个React应用要做的就是渲染,开发者关注的是渲染成成什么样子,而不用关心如何实现增量渲染。
把React的理念归结为一个公式,如下:
UI=render(data)
用户看到的界面(UI),应该是一个函数(在这里叫render)的执行结果,只接受数据(data)作为参数。这个函数是一个纯函数,所谓纯函数,指的是没有任何副作用,输出完全依赖于输入的函数,两次函数调用如果输入相同,得到的结果也绝对相同。
如此一来,最终的用户界面,在render函数确定的情况下完全取决于输入数据。
对于开发者来说,重要的是区分开哪些属于data,哪些属于render,想要更新用户界面,要做的就是更新data,用户界面自然会做出响应,所以React实践的也是“响应式编程”(ReactiveProgramming)的思想,这也就是React为什么叫做React的原因。
浏览器为了渲染HTML格式的网页,会先将HTML文本解析以构建DOM树,然后根据DOM树渲染出用户看到的界面,当要改变界面内容的时候,就去改变DOM树上的节点。
Web前端开发关于性能优化有一个原则:尽量减少DOM操作。虽然DOM操作也只是一些简单的JavaScript语句,但是DOM操作会引起浏览器对网页进行重新布局,重新绘制,这就是一个比JavaScript语句执行慢很多的过程。
如果用jquery的开发一个表格,性能测试时我们拿出1000条数据,请求加载,1秒后早已经从后端拿到数据。但页面可能半分钟都没有响应,陷入假死状态。面对这样的性能,以jquery作为开发语言
在react的实现方式中,VirutalDOM不会触及浏览器的部分,只是存在于JavaScript空间的树形结构,每次自上而下渲染React组件时,会对比这一次产生的VirtualDOM和上一次渲染的VirtualDOM,对比就会发现差别,然后修改真正的DOM树时就只需要触及差别中的部分就行。以计数器为例:
document.querySelector('#show').innerText='1'