React基礎(chǔ)知識
React 是一個聲明式啃匿、高效馏锡、靈活的雷蹂、創(chuàng)建用戶界面的JavaScript庫,本質(zhì)是將圖形界面(GUI)函數(shù)化杯道。
Universal渲染 指的是一套代碼可以同時在服務(wù)端和客戶端渲染匪煌。
Redux 是一個JavaScript狀態(tài)容器,提供可預(yù)測的狀態(tài)管理党巾。
Webpack 是當(dāng)下最熱門的前端資源模塊化管理和打包工具萎庭。
開發(fā)服務(wù)器是可以為程序提供資源服務(wù)的服務(wù)器。通常情況下齿拂,在頁面中引入的腳本文件等靜態(tài)資源都放在硬盤中驳规,使用了開發(fā)服務(wù)器,這些資源將被讀到內(nèi)存里署海,然后可以通過開發(fā)服務(wù)器的端口訪問這些資源吗购。webpack-dev-server就是開發(fā)服務(wù)器。
組件 是一個函數(shù)或類砸狞,它決定了如何把數(shù)據(jù)變?yōu)橐晥D捻勉。ReactElement是一個普通對象遣总,描述了組件實例或DOM節(jié)點旗芬;組件實例則是組件類的實例化對象。
ReactElement 就是大名鼎鼎的“虛擬DOM”蝴罪,其本質(zhì)是一個不可變對象研底,描述了一個組件的實例或一個DOM節(jié)點禽捆,包含組件的類型、屬性以及子元素等信息飘哨。ReactElement不是組件的實例,不能在ReactElement中調(diào)用React組件的任何方法琐凭,它只是告訴React你想在屏幕上顯示什么芽隆。JSX中的閉合標(biāo)簽就是ReactElement。對一個組件而言统屈,props就是輸入胚吁,ReactElement就是輸出,也就是render方法的返回值愁憔。
組件實例 是組件類的實例化對象腕扶,同樣被用來管理內(nèi)部狀態(tài)、處理生命周期函數(shù)吨掌。ReactDOM.render方法返回的就是組件實例半抱,某些組件方法中的this也指向組件實例脓恕,利用組件的Refs也可以獲取組件實例。無狀態(tài)函數(shù)是沒有實例化對象的窿侈,因此無法使用生命周期函數(shù)炼幔,也沒有內(nèi)部狀態(tài)。
JSX返回多個組件
JSX中返回多個組件史简,如果不想包面包一層父節(jié)點乃秀,可以使用數(shù)組,注意要有key屬性圆兵。
const arr = [
<h3 key={0}>第一個組件</h3>,
<h2 key={1}>第二個組件</h2>,
];
組件間交互方式
我個人常用的組件間交互的方式有:
如果是父子關(guān)系的組件跺讯,可以通過props由父組件將屬性和回調(diào)方法傳遞給子組件進(jìn)行交互
如果是兄弟關(guān)系的組件,可以將需要交互的屬性和方法提取到共同的父組件中殉农,通過props傳遞給這兩個兄弟組件進(jìn)行交互
如果是兄弟關(guān)系的組件刀脏,如A、B统抬,且需要在B中使用A的屬性火本,這個屬性是A特有的,不能直接提取到公共父組件中聪建,那么可以在父組件的state中添加一個屬性钙畔,如form,初始值為null金麸,在A的可以componentDidMount方法中對父組件的form屬性進(jìn)行賦值擎析,這樣就可以讓B使用A特有的屬性
可以使用redux進(jìn)行交互
render方法外渲染組件
在render方法外也可以渲染組件,但需要在render方法中預(yù)留好div挥下,如
// 正常渲染的組件
class CommonComponent extends Component {
render() {
return (
<div>
<Button>正常顯示的組件</Button>
<div id="specialComponent"></div>
</div>
);
}
}
// 在其他地方揍魂,個人認(rèn)為應(yīng)該在componentDidMount方法中
componentDidMount() {
const dom = document.getElementById('specialComponent');
if (dom !== null) {
ReactDOM.render(<Input placeholder="render外渲染的組件" />, dom);
}
}
注意:
如果采用這種特殊方式渲染的組件引用到了其父組件的props或state,還需要在componentDidUpdate方法中進(jìn)行同樣的在操作棚瘟,否則這個特殊方式渲染的組件不會刷新现斋。
componentDidUpdate() {
const dom = document.getElementById('specialComponent');
if (dom !== null) {
ReactDOM.render(<Input placeholder="render外渲染的組件" />, dom);
}
}
應(yīng)避免使用style屬性
React可以通過給組件設(shè)置style屬性,進(jìn)行CSS樣式控制偎蘸,但是不建議這么做庄蹋,最好是通過指定className來進(jìn)行樣式控制,方便維護(hù)迷雪。
使用數(shù)據(jù)結(jié)構(gòu)解決問題
如果組件本身的屬性無法支持某些功能限书,可以考慮通過定義數(shù)據(jù)結(jié)構(gòu)進(jìn)行解決,如對屬性值進(jìn)行拼接章咧,再拆分倦西,如菜單的key以ID%|%NAME的形式出現(xiàn)。
與redux關(guān)聯(lián)的組件ref問題
如果組件(如QueryChoose)組件赁严,是與redux相關(guān)聯(lián)的扰柠,對于使用react-redux的情況粉铐,也就是組件外調(diào)用了connect方法,需要在connect方法的mapStateToProps參數(shù)的返回對象中耻矮,添加ref屬性(如ref:'innerQueryChoose')秦躯,在外層組件A引用到QueryChoose時指定ref屬性(如ref:'wapperQueryChoose'),則在A中調(diào)用QueryChoose組件的方法或?qū)傩詴r裆装,這樣引用踱承。
// A組件中
this.refs.wapperQueryChoose.refs.innerQueryChoose.someMethod();
setTimeout更新state
有時使用this.setState()更新組件的狀態(tài),不會反映到頁面上哨免,具體什么時候可以反映到頁面上還不清楚茎活,目前發(fā)現(xiàn)會出現(xiàn)這種現(xiàn)象的地方有兩處:Tabs.TabPane內(nèi)部的組件、Modal內(nèi)部的組件琢唾。需要使用setTimeout才能達(dá)到更新狀態(tài)的效果载荔。
setTimeout(() => {
this.setState({
newState
});
},0);
帶著信息跳轉(zhuǎn)路由
跳轉(zhuǎn)到新路由時,需要帶有某些信息采桃,并且不想在url中顯示這些信息
this.context.router.push({
pathname: 'xxxx',// 跳轉(zhuǎn)的新路由
state: {}// 信息放在這里
});
在路由跳轉(zhuǎn)的頁面中懒熙,獲取這些信息
const urlInfo = this.props.location.state;
Smart Components and Dumb Components
也有叫Container Components and Presentational components的。
Smart component:
是連接Redux的組件(@connect)普办,一般不可復(fù)用工扎,類似MVC的C層
描述事物怎樣工作
不提供DOM標(biāo)簽和樣式
提供數(shù)據(jù),進(jìn)行數(shù)據(jù)獲取
發(fā)起action
存放在containers目錄
Dumb component:
是純粹的組件衔蹲,一般可復(fù)用肢娘,類似MVC的V層
描述事物的樣子
不依賴應(yīng)用
直接收props,包括數(shù)據(jù)和回調(diào)方法
很少有自己的state舆驶,有也是跟UI相關(guān)的
存放在components目錄
兩者的共同點是:無狀態(tài)橱健,或者說狀態(tài)提取到上層,統(tǒng)一由 redux 的 store 來管理沙廉。redux state -> Smart component -> Dumb component -> Dumb component(通過 props 傳遞)拘荡。在實踐中,少量 Dumb component 允許自帶 UI 狀態(tài)信息(組件 unmount 后撬陵,不需要保留 UI 狀態(tài))俱病。
值得注意的是:Smart component 是應(yīng)用更新狀態(tài)的最小單元。實踐中袱结,可以將 route handlers 作為 Smart component,一個 Smart component 對應(yīng)一個 reducer途凫。
ref回調(diào)函數(shù)
今天無意看到了ref可以使用回調(diào)函數(shù)的用法垢夹,這個回調(diào)函數(shù)在組件安裝后立即執(zhí)行,被引用的組件作為一個參數(shù)傳遞维费,且回調(diào)函數(shù)可以立即使用這個組件果元,或保存供以后使用(或?qū)崿F(xiàn)這兩種行為)促王。
<TextInput ref={(node) => (this._input = node)} />
將函數(shù)傳遞到父組件的state中
+---------------------------------------------------------+
| +---------+ |
| |C | A |
| +---------+ |
+---------------------------------------------------------+
| |
| Dashboard |
| |
| |
| +---------------------+ +----------------------+ |
| | B | | | |
| | + + | +---------> | |
| | | | | | | |
| | | + | | +-------------> | |
| | | | + | | | | |
| | | | | | | | | |
| +-+---+----+-----+----+ +----------------------+ |
| |
+---------------------------------------------------------+
A組件是B、C組件的公共祖先組件而晒,C組件需要用到B組件的方法method蝇狼,首先想到的是把method放到A中定義,然后傳入B和C倡怎。但是如果不使用ref在A中定義無法獲取B私有的狀態(tài)和其它信息迅耘,而且實際中C是A的第一層子組件,B是A的第监署。我想到的解決方法是:A定義一個方法update接收參數(shù)更新自己的state颤专,將update方法傳入B中,B將method作為參數(shù)調(diào)用update方法钠乏,這樣就將method定義為了A的state的一個屬性栖秕,再傳入C中,即可實現(xiàn)C中調(diào)用B中定義的方法晓避。
修改state的問題
特別注意簇捍,當(dāng)state中的屬性是一個對象是,獲取該對象進(jìn)行屬性值的修改后俏拱,頁面當(dāng)前不會按新狀態(tài)顯示暑塑,從react調(diào)試工具看該組件的狀態(tài)時是修改后的值,可能操作幾下頁面會按新狀態(tài)顯示彰触。原因是react不能直接修改state梯投,如果是屬性值是對象,需要進(jìn)行深拷貝况毅,修改值后在setState就沒問題了分蓖。
依賴更新的問題
我和同事是同一套代碼,沒有任何不同尔许,由于他npm install太慢么鹤,我把我的node_modules打成壓縮包發(fā)給他,他的項目啟動后會報一些奇怪的警告味廊,我的就沒有蒸甜。執(zhí)行完npm update就好了,很不明白這是為什么余佛。
還有一個奇怪的問題柠新,更新了antd依賴后,使用Tabs組件直接使用<Tabs.TabPane>定義的內(nèi)容可以顯示辉巡,而調(diào)用一個方法返回<Tabs.TabPane>會正常顯示切換標(biāo)簽恨憎,切換后內(nèi)容是空的,不報任何錯誤或警告。將整個node_modules刪除后憔恳,重新安裝依賴就正常了。
Controller View Pattern
The ReactJS Controller View Pattern
refs取不到組件的方法
refs如果能取到組件的state和props钥组,但是取不到組件定義的方法,可能是因為這些方法沒有在構(gòu)造函數(shù)中bind(this)程梦。
關(guān)于setState
如果setState函數(shù)的新狀態(tài)沒有改變當(dāng)前狀態(tài),也會重新執(zhí)行render方法
setState第二個參數(shù)可以是個回調(diào)函數(shù)作烟,如果有些操作要求在界面重新渲染完成后進(jìn)行,可以放在回調(diào)函數(shù)中
class CompB extends React.Component {
render(){
console.log("觸發(fā)了CompB重新render")
return (
<div>
<h1>{this.props.value}</h1>
</div>
);
}
}
class CompA extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Hello, world!',
}
this.onClick = this.onClick.bind(this);
}
onClick() {
this.setState({
value: '你好拿撩!'
}, () => console.log("CompA的setState回調(diào)!"));
}
render(){
console.log("觸發(fā)了CompA重新render")
return (
<div>
<h1>{this.state.value}</h1>
<button onClick={this.onClick}>點擊</button>
<hr/>
<CompB value={this.state.value} />
</div>
);
}
}
forceUpdate函數(shù)
會導(dǎo)致組件本身及其包含的所有級別的子組件重新讀取压恒、計算與渲染影暴,與其同級的無關(guān)組件不會重新渲染
所有UI組件的生命周期函數(shù)都會按生命周期規(guī)則來執(zhí)行,如先進(jìn)入componentWillUpdate再進(jìn)入render
不會調(diào)用shouldComponentUpdate來檢查是否允許重新渲染
可以提供一個回調(diào)函數(shù)探赫,這個回調(diào)函數(shù)將在所有組件渲染完成后被調(diào)用
class CompB extends React.Component {
componentWillUpdate(){
console.log("觸發(fā)了CompB的componentWillUpdate:",this.props.value)
}
render(){
console.log("觸發(fā)了CompB重新render:",this.props.value)
return (
<div>
<h1>{this.props.value}</h1>
</div>
);
}
}
class CompA extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Hello, world!',
}
this.onClick = this.onClick.bind(this);
}
shouldComponentUpdate() {
console.log("進(jìn)入shouldComponentUpdate");
}
onClick() {
this.forceUpdate(() => console.log("渲染完成!!!"));
}
render(){
console.log("觸發(fā)了CompA重新render")
return (
<div>
<h1>{this.state.value}</h1>
<button onClick={this.onClick}>點擊</button>
<hr/>
<CompB value="CompA的子組件" />
</div>
);
}
}
ReactDOM.render(
<div><CompA /><CompB value="與CompA無關(guān)的組件"/></div>,
document.getElementById('example')
);
關(guān)于render函數(shù)
可以在自己的代碼中調(diào)用這個函數(shù)以重新刷新組件型宙,注意只刷新了組件本身,不會刷新子組件伦吠,但這不是一個好辦法妆兑。如在點擊按鈕的onClick方法中可直接調(diào)用this.render()方法。
class CompB extends React.Component {
render(){
console.log("觸發(fā)了CompB重新render")
return (
<div>
<h1>{this.props.value}</h1>
</div>
);
}
}
class CompA extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Hello, world!',
}
this.onClick = this.onClick.bind(this);
}
onClick() {
this.render();
}
render(){
console.log("觸發(fā)了CompA重新render")
return (
<div>
<h1>{this.state.value}</h1>
<button onClick={this.onClick}>點擊</button>
<hr/>
<CompB value={this.state.value} />
</div>
);
}
}
組件定義外的代碼
在React和React Native都發(fā)現(xiàn)毛仪,在每次系統(tǒng)刷新時(刷新瀏覽器或重新打開App程序)搁嗓,個人感覺會執(zhí)行一遍所有文件(在幾類文件中都輸出了日志),就像此時是對所有的組件進(jìn)行一遍聲明一樣箱靴,后面用到的地方才是正式的引用腺逛。這里需要注意的是,所有的文件都會在系統(tǒng)刷新時執(zhí)行一遍衡怀,如果在組件定義外部進(jìn)行了一些操作棍矛,如獲取數(shù)據(jù),那這個時候獲取對不對抛杨,如果獲取數(shù)據(jù)需要用戶登錄信息够委,這是很可能沒有登錄信息,有沒有做處理怖现。
children屬性
如果使用了{this.props.children}
顯示子組件慨绳,想給子組件傳遞一些屬性,可以使用
{React.cloneElement(this.props.children, {changeOpenState: this.changeOpenState})}
注意此時this.props.children
是Object,如果this.props.children
是數(shù)組脐雪,則需要遍歷每個元素執(zhí)行上面的cloneElement。
監(jiān)聽瀏覽器窗口大小變化
handleResize() {
console.log("輸出")
}
componentDidMount() {
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
巧用setTimeout
項目中遇到一個問題恢共,點擊按鈕執(zhí)行了一個更新值的操作战秋,然后調(diào)用保存方法,發(fā)現(xiàn)保存方法中獲取的值并不是更新后的值讨韭,感覺是更新后的值沒有執(zhí)行完頁面刷新呢脂信,就進(jìn)入了保存方法。由于setTimeout是在下一輪開始時進(jìn)行透硝,所以使用setTimeout可使更新在本輪刷新完成后在執(zhí)行保存操作狰闪,達(dá)到保存更新后的效果。
export function callSaveClickHandler() {
const newData = {
'COL1': '改變一個字段值',
};
this.props.cardActions.modifyCardChangedData(this.state.servDef.servDef.SERV_ID, newData, true, this.props.servType);
// 這樣做的目的是樣保存操作在下一輪開始時執(zhí)行濒生,這樣才能獲取到頁面更新的內(nèi)容
setTimeout(()=>this.refs.btnBar.saveClickHandler(), 0);
}
巧用路由
/list路由對應(yīng)的是List組件埋泵,該組件主體是一個Tabs組件,默認(rèn)選中的是第一個Tab丽声。在什么都不處理的情況下觉义,選中不同的Tab不會改變路由,當(dāng)進(jìn)入下一個頁面并想返回時晒骇,總是返回到同一個路由,并且每次選中的只能是第一個Tab徒坡,這樣的用戶體驗不好箍鼓,用戶想要的是在進(jìn)入其它頁面前選中的是哪個Tab,返回到上一頁面也應(yīng)該選中哪個Tab何暮。
我的解決方法是铐殃,在點擊不同Tab時添加回調(diào)函數(shù),在里面根據(jù)當(dāng)前選中哪個Tab更新一次路由(this.context.router.replace)坏逢,也就是路由為/list/tab1、/list/tab2肖揣、...這種形式浮入,這樣無論是哪種方式進(jìn)入到相應(yīng)的路由,只要從路由中取出第二個斜杠后的值彤断,就可以知道當(dāng)前頁面應(yīng)該選中哪個Tab了宰衙。這樣相比兩個獨立頁面的好處:非 tab 部分都是公用的睹欲,無需重新渲染;點擊 tab 切換后滾動條不會回到頂部劲蜻。
阻止默認(rèn)事件
項目中遇到的問題是考余,在使用Table組件時,定義了行點擊事件疫蔓,這時行首部的復(fù)選框身冬、行中帶連接的文字、輸入框等滚躯,點擊的時候都會觸發(fā)行點擊事件嘿歌。解決方式是在Table行上的組件外包一層div,并添加如下點擊事件丧凤。
function stopPropagation(e) {
e.stopPropagation();
e.nativeEvent.stopImmediatePropagation();
}
Context
使用props可以進(jìn)行上下層組件間屬性的傳遞愿待,使用context可以實現(xiàn)跨層組件間屬性的傳遞。適合使用context的場景包括床底登錄信息仍侥、當(dāng)前語言以及主題信息等。而且厨幻,react-redux的Provider組件就使用了context來傳遞store腿时。
class Level2 extends React.Component {
render() {
return (
<div id="parent2">
<p>{this.context.name}</p>
</div>
);
}
};
Level2.contextTypes = {
name: React.PropTypes.string.isRequired,
};
class Level1 extends React.Component {
render() {
return (
<div id="parent1">
<Level2 />
</div>
);
}
};
class Root extends React.Component {
// 注意如果name的屬性值為null批糟,會報警告看铆,如下圖所示
getChildContext() {
return {name: '頂層屬性'};
}
render() {
return (
<div id="parent">
<Level1 />
</div>
);
}
};
Root.childContextTypes = {
name: React.PropTypes.string.isRequired,
};
ReactDOM.render(
<Root />,
document.getElementById('example')
);
在使用中發(fā)現(xiàn)了一個問題弹惦,在一個組件提供的context值改變時棠隐,使用這個值的子節(jié)點并沒有接受到context的改變,沒有達(dá)到要跨級共享數(shù)據(jù)的效果啰扛。網(wǎng)上說可以在這個提供context組件值改變時執(zhí)行setState這樣就會更新了嗡贺,但通常使用context都是在組件頂層,這樣進(jìn)行整個重新渲染感覺不太好煞茫。