一、React生命周期
一个组件从出生到消亡,在各个阶段React提供给我们调用的接口,就是生命周期。
生命周期这个东西,必须有项目,才知道他们干嘛的。
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 <div>
<button onClick={()=>{this.setState({a:this.state.a+1})}}>改变a值</button>
<button onClick={()=>{this.setState({isShow:!this.state.isShow})}}>
显示/隐藏组件
</button>
{ this.state.isShow ? <Child a={this.state.a}></Child> : null }
</div>
}
};
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 (
<div>
<button onClick={()=>{this.setState({m:this.state.m+1})}}>改变m值</button>
子组件的m值:{this.state.m},
子组件接收a值:{this.props.a}
</div>
);
}
}
1.2 Updating阶段【更新过程】
当组件的props改变或state改变的时候触发,依次是:
componentWillReceiveProps(nextProps)
当收到新的props的时候触发
shouldComponentUpdate(nextProps,nextState)
【门神函数】当组件state或props改变时触发,这个函数需要return true或者false,表示是否继续进Updating阶段。如return false,视图将不再更新,大致是为了增加效率。
componentWillUpdate(nextProps, nextState)
当组件state或props改变时触发,用来在update的时候进行一些准备。
render()渲染方法,创建虚拟DOM
componentDidUpdate(prevProps, prevState)
当组件state或props改变时触发,用来进行一些更新的验证。组件更新完成后调用,此时可以获取最新的DOM节点,用来验证信息的。
在Updating阶段中,绝对不允许改变state、props,否则会死循环。
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 <div>
<button onClick={()=>{ this.setState({m: this.state.m + 1 })}}>改变m值</button>
<h2>子组件的m值:{this.state.m}</h2>
<h2>子组件接收父组件a值:{this.props.a}</h2>
</div>
}
}
上树阶段:
constructor
componentWillMount
render
componentDidMount
更新阶段:
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
下树阶段
componentWillUnmount
二、Virtual DOM和Diff算法(理论知识)
React会在内存中存储一份DOM的镜像,当有render发生的时候,此时会在内存中用Diff算法进行最小差异比较,实现最小的更新。
Virtual DOM是React、Vue中的一个很重要的概念,在日常开发中,前端工程师们需要将后台的数据呈现到界面中,同时要能对用户的操作提供反馈,作用到UI上…… 这些都离不开DOM操作。但是我们知道,频繁的DOM操作会造成极大的资源浪费,也通常是性能瓶颈的原因。于是React、Vue引入了Virtual DOM。Virtual DOM的核心就是计算比较改变前后的DOM区别,然后用最少的DOM操作语句对DOM进行操作。
现在需要将下图左边的DOM结构替换成右边的结构,这种情景在实战项目中是经常会遇到的。但是如果直接操作DOM的话,进行移除的话可能就是四次删除,五次插入,这种消耗是很大的。但是使用Virtual DOM,那就是比较两个结构的差异,发现仅仅改变了四次内容,一次插入。这种消耗就小很多,无非加上一个比较的时间。
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 之间加个缓存。CPU(JS)只操作内存(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 <div>
<button onClick={()=>{this.setState({a : this.state.a + 1})}}>按我</button>
<ul ref="list">
<li>A</li>
<li>B</li>
<li>{this.state.a}</li>
<li>D</li>
</ul>
</div>
}
}
当某一个组件状态、属性被更改时,它的子组件、孙组件都会被重新渲染,Virtual DOM也将会计算这些组件的DOM更新。
三、日历组件
3.1业务
日选择视图 |
年选择视图 |
月选择视图 |
|
|
|
3.2划分组件
组件划分就是根据第一直观印象即可,按结构、功能独立进行划分。
最大组件Canlendar,里面有数据year、month、date
点击一个按钮,可以显示弹出层,弹出层的组件名: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 <div>
我是Canlendar组件
</div>
}
}
app/App.js引入canlendar最大组件
import React from "react";
import Canlendar from "./components/canlendar";
export default class App extends React.Component {
constructor() {
super();
}
render(){
return <div>
<div>
出生日期:<Canlendar></Canlendar>
</div>
</div>
}
}
开始写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 <div className="canlendar">
<div className="inputBox">
{this.state.year}年{this.state.month}月{this.state.date}日
</div>
{
this.state.isShowMenu
?
<CanlendarMenu
year={this.state.year}
month={this.state.month}
date={this.state.date}
></CanlendarMenu>
:
null
}
</div>
}
}
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 <div className="canlendar_menu">
<PickerDate year={year} month={month}></PickerDate>
<ChooserDate year={year} month={month} date={date}></ChooserDate>
</div>
}
}
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 <div className="picker">
<div className="left">
<a className="btn" href="###">上1月</a>
</div>
<div className="center">
<a href="###">{year}</a>年
<a href="###">{month}</a>月
</div>
<div className="right">
<a className="btn" href="###">下1月 </a>
</div>
</div>
}
}
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 <div className="chooserDate">
<table>
<tbody>
<tr>
<th>日</th>
<th>一</th>
<th>二</th>
<th>三</th>
<th>四</th>
<th>五</th>
<th>六</th>
</tr>
<tr>
<td>
<span>31</span>
<span className="cd">初一</span>
</td>
</tr>
tr*6>td*7
</tbody>
</table>
</div>
}
}
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(
<tr key={i}>
{
// 数组会自动展开
arr.slice(i * 7, i * 7 + 7).map((item, index)=>{
return <td key={index}>
<span>{item.d}</span>
<span className="cd">{item.cd}</span>
</td>
})
}
</tr>
)
}
return domArr;
}
render() {
return <div className="chooserDate">
<table>
<tbody>
<tr>
<th>日</th>
<th>一</th>
<th>二</th>
<th>三</th>
<th>四</th>
<th>五</th>
<th>六</th>
</tr>
{this.showTable()}
</tbody>
</table>
</div>
}
}
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 <div className="canlendar">
<div className="inputBox" onClick={()=>{this.setState({"isShowMenu" : true})}}>
{this.state.year}年{this.state.month}月{this.state.date}日
</div>
{
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)}
></CanlendarMenu>
:
null
}
</div>
}
}
然后通过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 <div className="canlendar_menu">
<PickerDate setYear={setYear} setMonth={setMonth}></PickerDate>
<ChooserDate setYear={setYear} setMonth={setMonth} setDate={setDate}></ChooserDate>
</div>
}
}
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 <div className="picker">
<div className="left">
<a className="btn" href="###" onClick={()=>{this.prevMonth()}}>上1月</a>
</div>
<div className="center">
<a href="###">{year}</a>年
<a href="###">{month}</a>月
</div>
<div className="right">
<a className="btn" href="###" onClick={()=>{this.nextMonth()}}>下1月</a>
</div>
</div>
}
}
完善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(
<tr key={i}>
{
// 数组会自动展开
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)}}
>
<span className="d">{item.d}</span>
<span className="cd">{item.cd}</span>
</td>
})
}
</tr>
)
}
return domArr;
}
render() {
return <div className="chooserDate">
<table>
...
</table>
</div>
}
}
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 <div className="canlendar_menu">
<PickerDate year={year} month={month}></PickerDate>
{/*<ChooserDate year={year} month={month} date={date}></ChooserDate>*/}
<ChooserYear year={year} setYear={setYear}></ChooserYear>
</div>
}
}
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(
<tr key={i}>
<td>{baseYear + i - 20}</td>
<td>{baseYear + i - 10}</td>
<td className={classnames({"cur":baseYear + i == this.props.year})}>
{baseYear + i}
</td>
<td>{baseYear + i + 10}</td>
<td>{baseYear + i + 20}</td>
</tr>
)
}
return arr;
}
render() {
return <div className="chooserYear">
<table ref="table">
<tbody>
{this.showTable()}
</tbody>
</table>
</div>
}
}
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}
></ChooserDate>
}else if(this.state.view == "year"){
return <ChooserYear
year={year}
setYear={setYear}
setView={this.setView.bind(this)}
></ChooserYear>
} else if (this.state.view == "month") {
return <ChooserMonth
setMonth={setMonth}
setView={this.setView.bind(this)}
></ChooserMonth>
}
}
//定义Picker组件
const Picker = ()=>{
if(this.state.view == "date"){
return <PickerDate
year={year}
month={month}
setYear={setYear}
setMonth={setMonth}
setView={this.setView.bind(this)}
></PickerDate>
}else if(this.state.view == "year"){
return < PickerYear
year={year}
setYear={setYear}
></PickerYear >
}else if(this.state.view == "month"){
return null;
}
}
return <div className="canlendar_menu">
<Picker></Picker>
<Chooser></Chooser>
</div>
}
}
canlendar/PickerYear.js
import React from "react";
export default class PickerYear extends React.Component {
constructor() {
super();
}
render() {
const {year, setYear} = this.props;
return <div className="picker">
<div className="left">
<a className="btn" href="javascript:;" onClick={()=>{setYear(year-1)}}>
上1年
</a>
</div>
<div className="center">
{year}年
</div>
<div className="right">
<a className="btn" href="javascript:;" onClick={()=>{ setYear(year + 1)}}>
下1年
</a>
</div>
</div>
}
}
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 <div className="chooserMonth">
<table ref="table">
<tbody>
<tr>
<td data-m="1">1月</td>
<td data-m="7">7月</td>
</tr>
<tr>
<td data-m="2">2月</td>
<td data-m="8">8月</td>
</tr>
<tr>
<td data-m="3">3月</td>
<td data-m="9">9月</td>
</tr>
<tr>
<td data-m="4">4月</td>
<td data-m="10">10月</td>
</tr>
<tr>
<td data-m="5">5月</td>
<td data-m="11">11月</td>
</tr>
<tr>
<td data-m="6">6月</td>
<td data-m="12">12月</td>
</tr>
</tbody>
</table>
</div>
}
}
原文出处:https://www.cnblogs.com/rope/p/10741113.html
来源:oschina
链接:https://my.oschina.net/u/4355290/blog/3259533