一個組件從出生到消亡,在各個階段React提供給咱們調用的接口,就是生命週期。javascript
生命週期這個東西,必須有項目,才知道他們幹嗎的。css
這個階段在組件上樹的時候發生,依次是:html
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組件: java
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> ); } }
當組件的props改變或state改變的時候觸發,依次是:react
componentWillReceiveProps(nextProps)jquery
當收到新的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,不然會死循環。
就一個函數: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
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更新。
日選擇視圖 |
年選擇視圖 |
月選擇視圖 |
|
|
|
組件劃分就是根據第一直觀印象便可,按結構、功能獨立進行劃分。
最大組件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> } }