有槽先吐
花了几天时间,大致读完了《深入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}>
![](qr.jpg)
</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;
}
}
**如果觉得有帮助,可以扫描二维码对我打赏,谢谢**
![](http:https://img.haomeiwen.com/i2898168/9262d2444b223aa5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
网友评论