js和jq操作DOM的时代
记得在很久很久以前,我们前端的代码都是写在一起的,什么css,js,html全部都是放在一起的。类似于下面的写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=<device-width>, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
.clickButton{
color:red;
width:50px;
height:40px;
}
</style>
</head>
<body>
<div id = "clickButton" class = "clickButton">
click me
</div>
</body>
</html>
<script type = "javascript/text">
window.onload = function(){
var buttonElement = document.getElementById("clickButton");
、、、
}
</script>
后面发现这样写的代码可读性以及可维护性非常低,所以就把js和css放到了单独的文件中去,这样维护起来比较方便,于是这样的写法火了很多年,我记得当时js操作DOM的语法还是比较复杂的,于是出现了Jquery这样的库,jQuery强大的选择器以及高度封装的API,让我们可以更方便的操作DOM,jQuery帮我们处理兼容性问题,同时也使DOM操作变得简单,确实前端的所有交互jquery都可以实现,可是总有大牛在思考,这样也没有解决效率问题,因为游览器中DOM的实现和ECMAScript是分离的,因此在使用js操作DOM的时候是通过js代码调用DOM的接口,这里插入网上的一个比喻:“把DOM和JavaScript各自想象为一个岛屿,它们之间用收费桥梁连接,js每次访问DOM,都要途径这座桥,并交纳“过桥费”,访问DOM的次数越多,费用也就越高”但是真正影响DOM操作性能的主要原因还是它会导致游览器重绘和重排。
MVVM框架诞生的时代
记得最开始推出来比较出名的MVVM框架是Angular,MVVM使用数据双向绑定,这使得开发者完全不需要操作DOM。更新了状态视图会自动更新,更新了视图数据状态也会自动更新,一下子使得前端的开发效率大幅提升。但是其大量的事件绑定使得其在复杂场景下的执行性能堪忧。为了兼顾开发效率与性能,虚拟DOM的概念由此而生。
虚拟DOM时代
虚拟的DOM的核心思想
提供一种方便的工具,进行最小化地DOM操作。简单来说就是DOM由JS托管,例如在修改页面元素以后,把渲染的工作交给js而不是浏览器,让js去计算哪些布局需要更改,再将它转换为真实DOM渲染出来,从而减小浏览器计算的复杂度。
理解虚拟DOM
Web页面是由一个个HTML元素嵌套组合而成的。当使用JavaScript来描述这些元素时,这些元素可以简单地被表示成纯粹的JSON对象的。 比如现在需要描述一个块级元素,我们都知道,HTML语法如下:
<div class = "button">
<span>click me</span>
</div>
其中包含了元素的类型和属性,我们可以用这样的JSON对象去描述这个块级元素
{
type:'div',
props:{
className:'button',
children:{
type:'span',
props:{
children:'click me'
}
}
}
}
实际上就是将DOM节点抽象成内存中的javascript对象,当元素更新的时候,会生成一个新的javascript对象,将这两个对象利用diff算法进行对比,最后将差异的部分应用到真实的dom树上。 用文字描述相对于比较简单,但是还是推荐大家去看一看下面大牛写的的文章,真正的理解虚拟dom
差点忘了,我们主讲的是react,前面都是一些铺垫,没有前面技术的迭代也不会出现react,直接开始进行react的剖析吧。
React时代
React是一个用于构建用户界面的JavaScript库,也是Web应用程序的视图层
react是什么?
React是一个用于构建用户界面的JavaScript库,也是Web应用程序的视图层
为什么使用react?
因为学习成本比较低,而且使用jsx语法支持在html中写js(写起来真的爽),并且是首款搭载虚拟dom的UI框架,所以选择它,没得错。
怎么使用react
这个就要慢慢的一步一步的开始说了。
组件
以前对于后端人员来说,万物皆对象,任何东西都可以抽象成对象然后new一个出来,包括女票,所以以前前端人员除了羡慕还是羡慕,因为没得办法new一个女票出来,但是现在前端应该有个概念,叫做万物皆组件,组件是React的核心、精髓。基于组件的应用开发是广泛使用的软件开发模式,用分而治之的方法,把一个大的应用分解成若干小的组件,每个组件只关注某个特定功能,但是把组件组合起来,就能构成一个功能庞大的应用。组件有输入、输出以及自身状态,分别对应props、render、state,相信稍微用过React的同学都很清楚。
组建创建方式
ES6的方式
import React,{Component} from 'react';
class App extends Component{
render(){
return (
<div>hellow word</div>
)
}
}
export default App;
createClass的方式
import React,{Component} from 'react';
const App = React.createClass({
getDefaultProps:function(){
return {name:'longhanghang'}
}
getInitialState:function(){
return {word:'hello word'}
}
render:function(){
return (<div>hellow word,{this.state.word},{this.props.name}</div>)
}
})
Functional Component
import React,{Component} from 'react';
function App(props){
const {name} = this.props;
return <div>hellow,{name}</div>
}
组件的prop
<MyButton className = "my-button" title = "click me" onClick = {myButtonAction} style = {{color:"red"}}></MyButton>
React组件的prop所能支持的类型除了字符串,还可以是任何一种JavaScript语言支持的数据类型。当prop的类型不是字符串时,在JSX中必须用花括号{}把值包裹,所以style的值有两层花括号,外层代表是JSX的语法,内层代表这是个对象常量。
React组件内部要反馈数据给外部世界,也是用prop,因为prop类型也可以是函数,函数类型的prop等于让父组件交给子组件一个回调函数,子组件在恰当的时机调用函数的prop,就可以把信息传递给外部世界。
我们现在来定义一个MyButton组件,看一看MyButton是怎么接受prop以及使用prop的
import React,{Component} from 'react';
export default class MyButton extends Component{
constructor(props){
super(props);
}
divClick = () => {
this.props.onClick(5);//通过回调将参数传递给父组件,从而实现子组件向父组件通讯
}
render(){
let {title,style} = this.props;
return(<div style = {style} onClick = {this.divClick}>{title}</div>)
}
}
ps:一定要在构造函数中调用super(props),否则无法通过this.props访问到父组件传递过来的props
组件的state
state
表示组件内部的状态,因为react组件不能修改父组件传递过来的props
,所以如果需要记录自身数据变化就必须用到state。
1 初始化state
state的初始化一般都是放在构造函数的结尾部分完成,如下
import React,{Component} from 'react';
export default class MyButton extends Component{
constructor(props){
super(props);
this.state = {
title:props.title
}
}
divClick = () => {
this.props.onClick(5);//通过回调将参数传递给父组件,从而实现子组件向父组件通讯
}
render(){
let {title,style} = this.props;
return(<div style = {style} onClick = {this.divClick}>{title}</div>)
}
}
其实state就是一个普通的javascript对象,这个对象里面记录这个组件中需要变化的数据
2 读取和更新state
import React,{Component} from 'react';
export default class MyButton extends Component{
constructor(props){
super(props);
this.state = {
title:props.title,
}
this.count = 0;
}
divClick = () => {
this.setState({
title:this.count++;
})
}
render(){
let {style} = this.props;
let {title} = this.state;
return(<div style = {style} onClick = {this.divClick}>{title}</div>)
}
}
ps:改变组件state必须要试用this.setState函数,而不能直接去修改this.state,因为直接赋值不会触发重新渲染,在界面上就看不到效果。
prop和state总结
现在有些同学可能会纠结react的数据有两种,第一个是prop,第二个是state,那什么情况下使用state,什么情况下使用prop,其实就一句话,prop是组件的对外接口,state是组件的内部状态,对外的数据就用prop管理,内部数据就用state管理
组件的生命周期
生命周期贯穿了react组件的一生,一个个钩子函数正是程序员操控react一生的窗口,从组件被定义的那一刻,注定是程序员的玩物,所以不要觉得生命周期难,让我用最简单的方式来学习生命周期吧。
react组件生命周期分为三个阶段:
1 组件创建阶段
2 组件更新阶段
3 组件卸载阶段
1 组件创建阶段
1 constructor(getDefaultProps,getInitialState)
很明显这个是构造函数,构造函数里面会初始化props和state以及创建this环境,所以组件创建之前的第一件事就是调用constructor钩子函数初始化props和state以及创建this环境。
es6的写法
export default class MyButton extends Component{
constructor(props){
super(props);
this.state = {
title:props.title,
}
this.count = 0;
}
}
createClass的写法(不推荐只是想让大家晓得)
import React,{Component} from 'react';
const App = React.createClass({
getDefaultProps:function(){
return {name:'longhanghang'}
}
getInitialState:function(){
return {word:'hello word'}
}
render:function(){
return (<div>hellow word,{this.state.word},{this.props.name}</div>)
}
})
相信大家已经看出来了,区别就是用constructor替代了之前的getDefaultProps,getInitialState。所以getDefaultProps,getInitialState这两个已经淡出了我们的视野,大家只需要知道,组件创建的第一步调用的方法是constructor即可
2 componentWillMont
这个钩子函数估计也会被淘汰了,因为没有什么实质的作用,constructor已经把组件的环境创建好了,那接下来就应该是创建组件,不明白这个钩子函数存在的意义,到目前为止我是从来没有使用过这个钩子函数,但是存在即道理,所以希望哪个大牛看了这篇文章能给我有建设性意义的指教
3 render
render是整个生命周期之中最重要的一个钩子函数,没有之一,它是一个纯函数,并不进行实质上的渲染动作,只是一个jsx描述的结构,最终都是react内部来进行渲染的,render中不能有任何的操作,页面的描述结果完全取决于state和prop,千万不要作死的在render中调用this.setState({}),这样会进入到死循环当中去。
4 componentDidMount
这个阶段组件已经被装载在dom树上了,你可以在这个函数里面去服务器上拉取数据然后更新dom,也可以在这里引入其它库的代码。
2 组件更新阶段
1 componentWillReceiveProps(nextProps)
这个钩子函数看字面意思是组件将要更新props,组件本身是不会更新props的,那么只能是在父组件中更新子组件的props,这种情况确实会调用这个钩子函数,但是实际上只要父组件的render函数被调用了,不管父组件传给子组件的props有没有被改变,都会触发子组件的componentWillReceiveProps过程。
2 shouldComponentUpdate(nextProps, nextState)
这个钩子函数和render配合来提高性能,render函数决定了改渲染什么,而shouldComponentUpdate决定了不需要渲染什么,黄金搭档
3 componentWillUpdate和componentDidUpdate
这两个钩子函数大家都能见名知意,就是组件将要被更新和组件已经被更新,当我们的state变化得时候,我们首先执行shouldComponentUpdate判断哪些需要更新,然后再执行componentWillUpdate,然后再执行render执行更新,最后再执行componentDidUpdate
组件卸载阶段
componentWillUnmount
这里写组件的遗言就好
生命周期总结
就用网上的一张图,多看几遍,自然就懂了
总结
作为一个前端的菜鸟,正在慢慢养成写文章的习惯,希望大佬些多多指教,同时我觉得学习的过程就是把复杂的问题简单化再把简单的问题复杂化,这篇文章我就是把复杂的react简单化了,后面我会把简单的react再复杂化,会深入的剖析每个点,然后再搞一个实战,让大家能轻松快乐的和我一起去体验react。再次感谢您的阅读。