前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发

一、React生命周期

一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期。

生命周期这个东西,必须有项目,才知道他们干嘛的。

前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第1张图片


1.1 Mouting阶段【装载过程

这个阶段在组件上树的时候发生,依次是:

constructor(props)     构造函数        作用:初始化state值,此时可访问props、发Ajax请求
componentWillMount()     组件将要上树    作用:常用于根组件中的引用程序配置,不能做任何涉及DOM的事情完成一些计算工作
render()                渲染组件        作用:创建虚拟DOM,组建的UI样式
componentDidMount()    组件已经上树    作用:启动AJAX调用,加载组件的数据,还能用ref得到DOM,添加事件监听

 

App.js

import React from "react";
import Child from "./Child.js";
export default class App extends React.Component{
    constructor(){
        super();
        this.state = {
            a:100,
            isShow:true
        }
    }
    render(){
        return 
{ this.state.isShow ? a={this.state.a}> : null }
} };

 

Child组件:  

import React from 'react';
export default class Child extends React.Component {
    constructor(){
        super();
        console.log("我是constructor构造函数");
        this.state = {
            m : 200
        }
    }
    //组件将要上树
    componentWillMount(){
        console.log("我是componentWillMount");
    }
    //组件已经上树
    componentDidMount(){
        console.log("我是componentDidMount");
    }

    render(){
        console.log("我是render");  
        return (
            
子组件的m值:{this.state.m}, 子组件接收a值:{this.props.a}
); } }

 


1.2 Updating阶段【更新过程

当组件的props改变或state改变的时候触发,依次是:

componentWillReceiveProps(nextProps)

当收到新的props的时候触发

 

shouldComponentUpdate(nextProps,nextState)

【门神函数】当组件stateprops改变时触发,这个函数需要return true或者false,表示是否继续进Updating阶段。如return false,视图将不再更新,大致是为了增加效率。

 

componentWillUpdate(nextProps, nextState)

当组件stateprops改变时触发,用来在update的时候进行一些准备。

 

render()渲染方法,创建虚拟DOM

 

componentDidUpdate(prevProps, prevState)

当组件stateprops改变时触发,用来进行一些更新的验证。组件更新完成后调用,此时可以获取最新的DOM节点,用来验证信息的。

 

Updating阶段中,绝对不允许改变stateprops,否则会死循环。


 

1.3 unmounting阶段【卸载过程】

就一个函数:componentWillUnmount()组件将要下树。

完整的Child.js子组件:

import React from 'react';
export default class Child extends React.Component {
    constructor() {
        super();
        console.log("我是constructor构造函数")
        this.state = {
            m:200
        }
    }
    //组件将要上树
    componentWillMount(){
        console.log("我是componentWillMount将要上树")
    }
    //组件已经上树
    componentDidMount(){
        console.log("我是componentDidMount已经上树")
    }

//************Updataing阶段【更新阶段】************** */
// 当组件的props或state改变时触发
componentWillReceiveProps(nextProps){
    console.log("更阶段的:componentWillReceiveProps", nextProps)
}
shouldComponentUpdate(nextProps, nextState) {
    console.log("更阶段的:shouldComponentUpdate", nextProps, nextState)
    return true;
}
componentWillUpdate(nextProps, nextState){
    console.log("更阶段的:componentWillUpdate", nextProps, nextState)
}
componentDidUpdate(prevProps, prevState){
    console.log("更阶段的:componentDidUpdate", prevProps, prevState)
}

//组件下树
    componentWillUnmount(){
        console.log("componentWillUnmount组件下树了");
}

    render(){
        console.log("我是render")
        return 

子组件的m值:{this.state.m}

子组件接收父组件a值:{this.props.a}

} }
示例代码

 

上树阶段:
    constructor
    componentWillMount
    render
    componentDidMount
更新阶段:
    componentWillReceiveProps
    shouldComponentUpdate
    componentWillUpdate
    render
    componentDidUpdate
下树阶段
    componentWillUnmount

二、Virtual DOMDiff算法(理论知识)

React会在内存中存储一份DOM的镜像,当有render发生的时候,此时会在内存中用Diff算法进行最小差异比较,实现最小的更新。

Virtual DOMReactVue中的一个很重要的概念,在日常开发中,前端工程师们需要将后台的数据呈现到界面中,同时要能对用户的操作提供反馈,作用到UI上…… 这些都离不开DOM操作。但是我们知道,频繁的DOM操作会造成极大的资源浪费,也通常是性能瓶颈的原因。于是ReactVue引入了Virtual DOMVirtual DOM的核心就是计算比较改变前后的DOM区别,然后用最少的DOM操作语句对DOM进行操作

 

现在需要将下图左边的DOM结构替换成右边的结构,这种情景在实战项目中是经常会遇到的。但是如果直接操作DOM的话,进行移除的话可能就是四次删除,五次插入,这种消耗是很大的但是使用Virtual DOM,那就是比较两个结构的差异,发现仅仅改变了四次内容,一次插入这种消耗就小很多,无非加上一个比较的时间。

 

前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第2张图片

前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第3张图片

React告诉我们的是在内存中维护一颗和页面一样的DOM树,这颗DOM树不是真正渲染在html中的,而是放在内存中的,因此修改它将会特别的快,并且资源消耗也少很多,当我们render一个页面的时候首先先将我们最新的DOM去和内存中的这棵虚拟DOM树去做对比(脏检查),然后对比出差异点,然后再用这棵虚拟DOM差异的部分去替换真正DOM树中的一部分。

 

这就是所谓的 Virtual DOM 算法。包括几个步骤:

  JavaScript 对象结构表示 DOM 树的结构;然后用这个树构建一个真正的 DOM 树,插到文档当中。

 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树差异。

 2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了。

 

Virtual DOM 本质上就是在 JS DOM 之间做了一个缓存。这个概念就和我们当初学操作系统一样,可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS DOM 之间加个缓存。CPUJS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。

App.js

import React from "react";
export default class App extends React.Component {
    constructor() {
        super();
        this.state = {
            a : 100
        }
    }
    componentDidMount(){
$(this.refs.list).find("li").css("position","relative").animate({"left":500},5000)
    }
    render(){
        console.log("我是render函数")
        return 
  • A
  • B
  • {this.state.a}
  • D
} }

当某一个组件状态、属性被更改时,它的子组件、孙组件都会被重新渲染,Virtual DOM也将会计算这些组件的DOM更新。

 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第4张图片


三、日历组件

3.1业务

日选择视图

年选择视图

月选择视图

 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第5张图片

 

 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第6张图片

 

 前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发_第7张图片

 

 


3.2划分组件

组件划分就是根据第一直观印象即可,按结构、功能独立进行划分。

最大组件Canlendar,里面有数据yearmonthdate

点击一个按钮,可以显示弹出层,弹出层的组件名:Canlendar_menu

下辖五个组件,五个组件都兄弟:

ChooserDate

 

 

ChooserYear

 

 

ChooserMonth

 

 

PickerDate

 

 

PickerYear

 

 

 

所有的组件不需要对兄弟组件负责,只需要对最大组件的数据负责。

 

【先实现日历视图】

index.html

<html>
<head>
    <title>日历组件title>
    <link rel="stylesheet" href="css/style.css" />
head>
<body>
    <div id="app">div>
    <script type="text/javascript" src="lib/jquery-2.2.4.min.js">script>
    <script type="text/javascript" src="dist/bundle.js">script>
body>
html>
示例代码

创建app/components/canlendar/index.js文件,这是最大的组件。

import React from "react";
export default class Canlendar extends React.Component {
    constructor() {
        super();
    }
    render() {
        return 
我是Canlendar组件
} }

 

app/App.js引入canlendar最大组件

import React from "react";
import Canlendar from "./components/canlendar";
export default class App extends React.Component {
    constructor() {
        super();
     
    }
    render(){
        return 
出生日期:
} }

 

开始写app/components/canlendar/index.js

import React from "react";
import CanlendarMenu from "./CanlendarMenu.js";
export default class Canlendar extends React.Component {
    constructor() {
        super();
        this.state = {
            year : 2018 ,
            month : 8 ,
            date : 8,
            isShowMenu : false
        }
    } 
    render() {
        return 
className="canlendar"> className="inputBox"> {this.state.year}{this.state.month}{this.state.date}
{ this.state.isShowMenu ? <CanlendarMenu year={this.state.year} month={this.state.month} date={this.state.date} > : null }
} }

css样式:

*{margin:0;padding:0;}
.canlendar{position: relative;}
.canlendar .inputBox{
    width: 150px;height: 20px;border: 1px solid #BDBDBD;
    border-radius:2px;position:relative;font-size:13px;
    padding:0 10px;color:#424242;line-height: 20px;
}
.canlendar .inputBox::before{
    content:"";position: absolute;right:0;top:0;
    width:20px;height:20px;background: #F5F5F5;
}
示例代码

 

开始写app/components/canlendar/CanlendarMenu.js弹出层

import React from "react";
import PickerDate from "./PickerDate.js";
import ChooserDate from "./ChooserDate.js";
export default class CanlendarMenu extends React.Component {
    constructor() {
        super();
    }
render() {
    //解构得到年、月、日
    const {year, month, date} = this.props;
        return 
className="canlendar_menu"> year={year} month={month}> year={year} month={month} date={date}>
} }

css样式:

.canlendar .canlendar_menu{
    position: absolute;top: 26px;left:0;z-index: 999;
    width:360px; height:260px;
    border: 1px solid #BDBDBD;
    box-shadow: 0px 0px 8px #00000036;
    border-radius: 5px;background:white;
}
示例代码

 

app/components/canlendar/PickerDate.js

import React from "react";
export default class PickerDate extends React.Component {
    constructor() {
        super();
    }
    render() {
        const {year, month} = this.props;
        return 
className="picker">
{year}{month}
} }

css样式:

.canlendar .picker{padding-top:10px;overflow: hidden;margin-bottom: 13px;}
.canlendar .picker .left{float: left;width:33.33%;text-align: center;}
.canlendar .picker .right{float:left;width:33.33%;text-align: center;}
.canlendar .picker .center{
    float: left;width:33.33%;text-align: center;font-size: 18px;font-weight: bold;
}
.canlendar .picker .btn{
    padding:4px 10px;background: #2196F3;font-size: 12px;
    border-radius: 4px;text-decoration: none;color: white;
}
.canlendar .chooserDate{color:#333;font-size: 12px;}
.canlendar .chooserDate span{display: block;}
.canlendar .chooserDate table{
    width:100%;text-align:center;line-height: 14px;border-collapse:collapse;
}
.canlendar .chooserDate span.cd{font-size: 10px;}
.canlendar .chooserDate table td{padding-bottom: 2px;cursor: pointer;}
.canlendar .chooserDate table th{line-height: 26px;}
.canlendar .chooserDate table td.gray{color: #c1bcbc;}
.canlendar .chooserDate table td.cur{background-color: #FFCDD2;}
示例代码

 

 

app/components/canlendar/ChooserDate.js

import React from "react";
export default class ChooserDate extends React.Component {
    constructor() {
        super();
    }
    render() {
        return  className="chooserDate">
            
                    tr*6>td*7
                
31 初一
} }
import React from "react";
import { solar2lunar } from "solarlunar";
import classnames from "classnames";
export default class ChooserDate extends React.Component {
    constructor() {
        super();
    }
    //显示表格
    showTable(){
        const {year , month , date} = this.props;
        //三要素
        var thisMonth1Day = new Date(year, month - 1 , 1).getDay();
        var thisMonthDateAmount = new Date(year, month, 0).getDate();
        var prevMonthDateAmount = new Date(year, month - 1, 0).getDate();
    
        var arr = [];
        //上个月的尾巴
        while(thisMonth1Day--){
            var d = prevMonthDateAmount--;
            var sl = solarLunar.solar2lunar(year, month - 1, d);
            arr.unshift({
                "d" : d,
                "cd": sl.term || sl.dayCn,
            })
        }
        //本月
        var count = 0;
        while (thisMonthDateAmount--){
        count++;
            var d = count;
            var sl = solarLunar.solar2lunar(year, month, d);
            arr.push({
                "d": d,
                "cd": sl.term || sl.dayCn,
            });
            
        }
        //下月开头
        var nextCount = 0;
        while(arr.length != 42){
        nextCount++;
            var d = nextCount;
            var sl = solarLunar.solar2lunar(year, month + 1, d);
            arr.push({
                "d": d,
                "cd": sl.term || sl.dayCn,
            });
        }
    //表格上树显示
        var domArr = [];
        for(var i = 0;  i < arr.length / 7; i++){
            domArr.push(
                
                    {
                        // 数组会自动展开
                        arr.slice(i * 7, i * 7 + 7).map((item, index)=>{
                            return 
                                {item.d}
                                {item.cd}
                            
                        })
                    }
                
            )
        }
        return domArr;
    }

    render() {
        return 
{this.showTable()}
} }

 

app/components/canlendar/index.js实现切换上月、下月

import React from "react";
import CanlendarMenu from "./CanlendarMenu.js";
export default class Canlendar extends React.Component {
    constructor() {
        super();
        this.state = {
            year : 2018 ,
            month : 8 ,
            date : 8,
            isShowMenu : false
        }
    } 
    setShowMenu(isShowMenu){
        this.setState({isShowMenu});
    }
    setYear(year){
        this.setState({ year });
    }
    setMonth(month){
        this.setState({ month });
    }
    setDate(date){
        this.setState({date});
    }
    render() {
        return 
onClick={()=>{this.setState({"isShowMenu" : true})}}> {this.state.year}年{this.state.month}月{this.state.date}日
{ this.state.isShowMenu ? <CanlendarMenu year={this.state.year} month={this.state.month} date={this.state.date} setYear={this.setYear.bind(this)} setMonth={this.setMonth.bind(this)} setDate={this.setDate.bind(this)} setShowMenu={this.setShowMenu.bind(this)} > : null }
} }

 

然后通过app/components/canlendar/CanlendarMenu.js继续往下传

import React from "react";
import PickerDate from "./PickerDate.js";
import ChooserDate from "./ChooserDate.js";
export default class CanlendarMenu extends React.Component {
    constructor() {
        super();
    }
render() {
    //解构得到年、月、日
    const {year, month, date, setYear, setMonth, setDate, setShowMenu} = this.props;
        return 
setYear={setYear} setMonth={setMonth}> setYear={setYear} setMonth={setMonth} setDate={setDate}>
} }

 

canlender/PickerDate.js

import React from "react";
export default class PickerDate extends React.Component {
    constructor() {
        super();
    }
    //下一月
    nextMonth(){
        //如果不是12月,此时月份加1 
        if(this.props.month != 12){
            this.props.setMonth(this.props.month + 1);
        }else{
            //如果是12月,此时月份变为1,年加1
            this.props.setMonth(1);
            this.props.setYear(this.props.year + 1);
        }   
    }
    //上一月
    prevMonth(){
        if(this.props.month != 1) {
            this.props.setMonth(this.props.month - 1);
        }else{
            this.props.setMonth(12);
            this.props.setYear(this.props.year - 1);
        }
    }
    render() {
        const {year, month} = this.props;
        return 
    }
}

 

完善app/components/canlendar/ChooserDate.js

添加类名,点击单元格切换

import React from "react";
import { solar2lunar } from "solarlunar";
import classnames from "classnames";
export default class ChooserDate extends React.Component {
    constructor() {
        super();
    }
    
    //点击某一个小格格改变年月日
    clickTd(d, isPrevMonth, isNextMonth){
        this.props.setDate(d);    //设置日子
        this.props.setShowMenu(false);    //关闭菜单
        if(isPrevMonth){
            var dd = new Date(this.props.year, this.props.month - 2, d);    //月份要重算
            this.props.setMonth(dd.getMonth() + 1);    //改变月份
            this.props.setYear(dd.getFullYear());    //改变年
        }else if(isNextMonth){
            var dd = new Date(this.props.year, this.props.month, d);    //月份要重算
            this.props.setMonth(dd.getMonth() + 1);    //改变月份
            this.props.setYear(dd.getFullYear());    //改变年
        }
    }

    //显示表格
    showTable(){
        const {year , month , date} = this.props;
        //三要素
           .......

        var arr = [];
        //上个月的尾巴
        var count = thismonth1day;
        while(count--){
            var d = prevmonthdateamount - count;
            var sl = solar2lunar(year, month - 1, d);
            arr.push({
                "d" : d,
                "cd": sl.term || sl.dayCn,
                "gray" : true ,
                "cur" : false ,
                "prevMonth" : true
            })
        }
        //本月
        var count = 1;
        while (count <= thismonthdateamount){
            var d = count;
            var sl = solar2lunar(year, month, d);
            arr.push({
                "d": d,
                "cd": sl.term || sl.dayCn,
                "gray": false ,
                "cur": date == d
            });
            count++;
        }
        //下月开头
        var count = 1;
        while(arr.length != 35 && arr.length != 42){
            var d = count++;
            var sl = solar2lunar(year, month + 1, d);
            arr.push({
                "d": d,
                "cd": sl.term || sl.dayCn,
                "gray" : true ,
                "cur" : false ,
                'nextMonth' : true
            });
        }

        var domArr = [];
        for(var i = 0 ;  i < arr.length / 7 ; i++){
            domArr.push(
                
                    {
                        // 数组会自动展开
                        arr.slice(i * 7, i * 7 + 7).map((item, index) => {
                            return <td 
                                key={index} 
                                className={classnames({"gray":item.gray, "cur":item.cur})}
                                onClick={()=>{this.clickTd(item.d, item.prevMonth, item.nextMonth)}}
                            >
                                {item.d}
                                {item.cd}
                            
                        })
                    }
                
            )
        }
        return domArr;
    }
    render() {
        return 
...
} }

 

app/components/canlendar/CanlendarMenu.js切换视图

import React from "react";
import PickerDate from "./PickerDate.js";
import ChooserDate from "./ChooserDate.js";
import ChooserDate from "./ChooserYear.js";
export default class CanlendarMenu extends React.Component {
    constructor() {
        super();
    }
render() {
    //解构得到年、月、日
    const {year, month, date} = this.props;
        return 
{/**/}
} }

 

app/components/canlendar/ChooserYear.js

import React from "react";
import classnames from "classnames";
export default class chooserYear extends React.Component {
    constructor() {
        super();
    }
    //组件上树之后
    componentDidMount(){
        var self = this;
        //事件委托,因为td太多了
        $(this.refs.table).on("click","td", function(){
            //得到你点击的小格格里面的内容,内容就是年份
            var year = $(this).html();
            self.props.setYear(year);        //设年
            self.props.setView("date");     //回到日视图
        });
    }
 
    //显示表格
    showTable(){
        //算出基数年,比如当前2018年,基数年就是2010年。就是年份减去“零头”。
        const baseYear = this.props.year - this.props.year % 10;
        var arr = [];
        for(var i = 0; i < 10 ; i++){
            arr.push(
                
                    {baseYear + i - 20}
                    {baseYear + i - 10}
                    className={classnames({"cur":baseYear + i == this.props.year})}>
              {baseYear + i}
            
                    {baseYear + i + 10}
                    {baseYear + i + 20}
                
            )
        }
        return arr;
    }
    render() {
        return 
ref="table"> {this.showTable()}
} }

CSS样式:

.canlendar .chooserYear table .cur{color:red;font-weight: bold;}
.canlendar .chooserMonth table{
    width:100%;text-align: center;line-height: 40px;
}
.canlendar a{
    color: #2196F3;text-decoration: none;padding: 0 3px;
}
示例代码

 

canlendar/CanlendarMenu.js

import React from "react";
import PickerDate from "./PickerDate.js";
import PickerYear from "./PickerYear.js";
import ChooserDate from "./ChooserDate.js";
import ChooserYear from "./ChooserYear.js";
import ChooserMonth from "./ChooserMonth.js";
export default class CanlendarMenu extends React.Component {
    constructor() {
        super();
        this.state = {
            view : "date"  //当前的视图date、month、year
        }
    }
    //设置视图
    setView(view){
        this.setState({view});
    }
    render() {
        //解构得到年、月、日
        const { year, month, date, setYear, setMonth, setDate, setShowMenu} = this.props;
         
        //定义Chooser组件
        const Chooser = ()=>{
            //根据state的view属性的值,来决定真实的chooser
            if(this.state.view == "date"){
                return <ChooserDate
                    year={year}
                    month={month}
                    date={date}
                    setYear={setYear}
                    setMonth={setMonth}
                    setDate={setDate}
                    setShowMenu={setShowMenu}
                >
            }else if(this.state.view == "year"){
                return <ChooserYear
                    year={year}
                    setYear={setYear}
                    setView={this.setView.bind(this)}
                >
            } else if (this.state.view == "month") {
                return <ChooserMonth
                    setMonth={setMonth}
                    setView={this.setView.bind(this)}
                >
            }
        }

        //定义Picker组件
        const Picker = ()=>{
            if(this.state.view == "date"){
                return <PickerDate 
                    year={year} 
                    month={month} 
                    setYear={setYear} 
                    setMonth={setMonth} 
                    setView={this.setView.bind(this)}
                > 
            }else if(this.state.view == "year"){
                return < PickerYear
                    year={year}
                    setYear={setYear}
                >
            }else if(this.state.view == "month"){
                return null;
            }
        }

        return 
} }

 

canlendar/PickerYear.js

import React from "react";
export default class PickerYear extends React.Component {
    constructor() {
        super();
    }
    render() {
        const {year, setYear} = this.props;
        return 
    }
}

canlendar/ChooserMonth.js

import React from "react";
import classnames from "classnames";
export default class chooserMonth extends React.Component {
    constructor() {
        super();

    }
    componentDidMount(){
        //事件委托
        var self = this;
        $(this.refs.table).on("click","td", function(){
            self.props.setMonth(parseInt($(this).data("m")));
            self.props.setView("date");
        })
    }
    render() {
        return 
ref="table">
1月 7月
2月 8月
3月 9月
4月 10月
5月 11月
6月 12月
} }

 


 

你可能感兴趣的:(前端笔记之React(四)生命周期&Virtual DOM和Diff算法&日历组件开发)