有槽先吐
花了幾天時間,大致讀完了《深入React技術棧》,簡單總結的話,不及預期。
作者成書前,在知乎開設pure render專欄,更新過一系列react主題文章。看過其中幾篇,認為是內容很不錯的文章,因此對成書期望很高,希望能在書中對react相關內容有一個真正全面深入的理解。
然而,在實際閱讀過程中,總有一種茫然的感覺。個人感覺,書中所述,不見整體,陷入細節。
第三章《解讀React源碼》,書中內容缺少對React整體設計的解構,很快的陷入到細節中,附上一些具體實現的源碼。對于一個本身相對復雜的內容,這種寫法讀完會覺得摸不著頭腦。
寫博文和寫書應當是兩種寫作方法,專欄中的文章,基于作者分享知識,大家的要求不會太高,有一點收貨都會滿意。出版成書,就不能只是將博文整理成冊了,讀者對于書的期望,顯然是比博文更高。
一些收獲
作者對react技術棧,應該是有深入的理解的,不太認可的,是這本書的表述方式。讀過一遍,也有一點收獲。
合成事件與原生事件混用的問題
想實現頁面中有一個二維碼,點擊二維碼不隱藏,點擊二維碼以外地方,隱藏二維碼的功能
//為body綁定原生click
componentDidMount(){
document.body.addEventListener('click',e=>{
this.setState({active:false})
})
}
//合成事件
handleClickQr(e){
e.preventDefault();
}
render(){
return (
<div classname='code' style={{display:this.state.active ? 'block' : 'none'}} onclick = {this.handleClickQr}>

</div>
)
}
預期點擊二維碼時,阻止默認事件,不隱藏二維碼。實際效果是點擊二維碼區域,也會導致隱藏。原因是React合成事件系統的委派機制,事件并沒有綁定到div.qr元素上。
解決方法有兩個
- 不要將合成事件與原生事件混用
componentDidMount(){
document.body.addEventListener('click',e=>{
this.setState({active:false})
});
document.querySelector('.qr').addEventListener('click',e=>{
e.preventDefault();
})
}
- 通過事件對象e.target判斷
componentDidMount(){
document.body.addEventListener('click',e=>{
if(e.target && e.target.matchs('div.code')){return}
this.setState({active:false})
});
}
理解setState機制
class Example extends Component {
constructor(){
super();
this.state={val:0};
}
componentDidMount(){
this.setState({val:this.state.val+1});
console.log(this.state.val);//第一次輸出
this.setState({val:this.state.val+1});
console.log(this.state.val);//第二次輸出
setTimeout(()=>{
this.setState({val:this.state.val+1});
console.log(this.state.val);//第三次輸出
this.setState({val:this.state.val+1});
console.log(this.state.val);//第四次輸出
},0)
}
}
上述代碼,分別輸出:0、0、2、3
理解原因,需知setState機制。
- setState 底層批量更新
- 批量更新過程由事務控制
- 前兩次setState,整個react組件渲染到DOM的過程已經處于一個大的事務中,batchingStrategy的isBatchingUpdates已經被設為true,所以兩次setState的結果并沒有立即生效,而是被放進了dirtyComponents中。
- setTimeout中執行的兩次setState,因為與初始react組件渲染過程在不同的事件循環,沒有前置的batchedUpdate調用,batchingStrategy的isBatchingUpdates標志位是false,使得新的state立即生效。
redux應用的架構
一個典型的redux應用結構是類型下面
在一些功能簡單的應用中,可以像上面這樣按照類型劃分文件結構。但是在大型應用中,會存在很多組件,如果仍以類型劃分,會導致如actions目錄下,有非常多的action.js文件,很難快速定位文件。因此在大型應用中,可以采用混合方式劃分文件結構。
在上述結構中,首先將redux中的組件,劃分為了三種不同的組件,Layouts、Views、Compoents
-
Layouts是頁面布局組件,描述頁面的基本結構,目的是將主框架與頁面主體內容分離
const Layout = ({children}) => (
<div classname = 'container'>
<Header/>
<div classname = 'content'>
{children}
</div>
</div>
)
* Views是子路由入口組件,描述子路由入口基本結構,包含此路由下所有展示型組件。為了保持子組件的純凈,我們在這一層組件中定義數據和action的入口,將他們分發到子組件中去。Views就是Redux中的容器型組件
@connect(state => {
...
})
class HomeView extends Component {
render(){
const {sth,changeType} = this.props;
const cardProps = {sth,changeType};
return (
<div>
<Card {...cardProps}/>
</div>
)
}
}
* Components就是末級渲染組件,包含了具體的業務邏輯和交互,但是所有的數據和actions都是從Views傳下來的,意味著它們可以完全脫離數據層而獨立存在的展示型組件。
class Card extends Components {
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(opts){
const {type} = opts;
this.props.changeType(type);
}
render(){
const {sth} = this.props;
return (
<div>
<Switch onChange = {this.handleChange}>
...
</Switch>
{sth}
</div>
)
}
}
理解了三種類型組件,再來看views/和componets文件夾。views文件夾中,存放的每個路由的入口頁,如首頁(Home)。每一個入口都會有三個文件,*.js是入口組件,*.css是樣式,*Redux.js是components/Home文件夾下所有reducer和action的聚合。
在components/Home文件夾里,是當前路由對應的頁面Home需要的所有內容--components、actions、reducers、樣式等。
views/HomeRedux.js示例
import { combineReducers } from 'redux';
// 引入 reducer 及 actionCreator
import list, { loadArticles } from '../components/Home/PreviewListRedux';
export default combineReducers({ list,});
export const actions = { loadArticles,};
components/Home/PreviewListRedux.js示例
const initialState = {
loading: true,
error: false,
articleList: [],
};
const LOAD_ARTICLES = 'LOAD_ARTICLES';
const LOAD_ARTICLES_SUCCESS = 'LOAD_ARTICLES_SUCCESS';
const LOAD_ARTICLES_ERROR = 'LOAD_ARTICLES_ERROR';
export function loadArticles() {
return {
types: [LOAD_ARTICLES, LOAD_ARTICLES_SUCCESS, LOAD_ARTICLES_ERROR],
url: '/api/articles.json',
};
}
export default function previewList(state = initialState, action) {
switch (action.type) {
case LOAD_ARTICLES: {
return {
...state,
loading: true,
error: false,
};
}
case LOAD_ARTICLES_SUCCESS: {
return {
...state,
loading: false,
error: false,
articleList: action.payload,
};
}
case LOAD_ARTICLES_ERROR: {
return {
...state,
loading: false,
error: true,
};
}
default:
return state;
}
}
**如果覺得有幫助,可以掃描二維碼對我打賞,謝謝**
