基于上上周的学习,搭建了webpack+React的项目环境,这两周主要就在写业务代码并基于业务需求继续学习React和穿插的其他一些小知识,React入门主要参考了以下几个系列的文章,在理解页面DOM渲染,视图层等概念的基础上,还是非常浅显易懂的:
玩转 React(一)- 前言
React 深入系列1:React 中的元素、组件、实例和节点
以下记录了对一些概念的理解以及部分思考:
- props和state有什么区别,如何使用,父子组件之间传值;
- 页面之间如何传值,怎么拿到类似全局变量的一个值(react-redux相关)
- 如何触发render更新组件,如何避免不必要的render调用(理解生命周期)
- 几个细节(props覆盖,条件表达式简化,组件初始化,表单重置,render嵌套HTML标签)
1. props和state有什么区别,如何使用,父子组件之间传值
我知道React是一个视图层的框架,在之前使用的JQuery中,我们通过编写HTML代码来设计网页的结构,通过 jquery选择器$("#")
以及getElementById等 api 来获取某个节点,通过节点的 innerHTML,innerText,appendChild 等属性或者方法来更新视图,但是在React,我只需要关注数据的更新,React会帮我完成视图的更新。
所以,在我看来,定义一个React组件,可以分为两部分,一部分用来定义处理数据,一部分用来render返回react元素渲染DOM。
因此,各组件和页面间的传值成了我首要考虑的问题也是遇到问题最多的地方,而组件根据props和state计算得到对应页面的UI,这两个参数有什么区别,如何定义呢?
- props 是组件对外的接口,state 是组件对内的接口,组件通过state参数渲染元素,上下层组件通过props参数传递数据,state是可变的,props是只读属性;
- 不能直接修改state,采用setState方法,state属性改变会重发render更新组件做到视图和数据的绑定;
- 调用setState,组件的state并不会立即改变,setState只是把要修改的状态放入一个队列中,React会优化真正的执行时机,并且React会出于性能原因,可能会将多次setState的状态修改合并成一次状态修改。所以不能依赖当前的state,计算下个state。
因此一个完整的父子组件之间传值的流程如下(以主组件调用侧栏组件为例):
- 子组件props属性定义及类型校验
SlideFrame.propTypes = {
width: React.PropTypes.any, //宽度
title: React.PropTypes.string, //标题
show: React.PropTypes.bool, //是否显示
hasMask: React.PropTypes.bool, //是否有遮罩层
onClose: React.PropTypes.func, //点击遮罩层或右上方x时触发的事件
content: React.PropTypes.oneOfType([React.PropTypes.func, React.PropTypes.string]), //内容component,包裹后的元素添加this.props.close方法进行侧滑关闭
afterClose: React.PropTypes.func, //关闭后触发的事件,用于更新外层的show值
params: React.PropTypes.object, //外部传入内部组件props
hasFooter: React.PropTypes.bool //是否有低端操作区
};
SlideFrame.defaultProps = {
width: '50vw',
onClose: ()=>{},
okText: '保存',
cancelText: '取消',
hasMask: true,
afterClose: ()=>{},
params: {},
hasFooter: true
};
- 父组件传值
<SlideFrame title={ showSlideFrameContent.isNew ? messages('rep.distribution.details.create')/*新建分配*/ : messages('rep.distribution.details.edit')/*编辑分配*/}
show={showSlideFrame}
content={ReportDistributionMaintain}
onClose={this.closeSlide}
params={showSlideFrameContent}
/>
- 父组件更新state值,重发render更新传入子组件的值
ReportDistributionService.getCopyReportDetail(record.reportLineOID).then((response) => {
this.setState({
loading: false,
showSlideFrameContent :{
isNew: false,
reportDetail: response.data,
reportLineOID: record.reportLineOID
}
})
});
ReportDistributionService.getDistributionPeopleList(page, pageSize,record.reportLineOID).then((response) => {
response.data.map((item,index) => {
item.index = index + page * pageSize + 1;
item.key = item.userOID;
item.departmentName = item.department.name;
});
this.setState({
loading: false,
showSlideFrameContent :{
isNew: false,
pagination: {
total: Number(response.headers['x-total-count']) ? Number(response.headers['x-total-count']) : 0,
current: this.state.page + 1
},
distributionPeopleList: response.data,
reportLineOID:record.reportLineOID
}
});
});
- 子组件接收props
componentWillReceiveProps(nextProps) {
//console.log("子组件接收props");
this.setState({loading: true});
// props每变一次就会调用一次,所以赋值如果写一起会被空值覆盖
if (nextProps.params.reportDetail) {
console.log(nextProps.params.reportDetail);
this.setState({
isNew: nextProps.params.isNew,
reportDetail:nextProps.params.reportDetail,
reportLineOID: nextProps.params.reportLineOID
}, () => {
if(nextProps.params.reportDetail.dataArea === "5"){
this.getCopyReportCorporation(); //获取 单条 报表副本 已选法人列表
}
if(nextProps.params.reportDetail.dataArea === "8"){
this.getCheckedSetOfBookList(); //获取 单条 报表副本 已选账套列表
}
if(nextProps.params.reportDetail.dataArea === "4" || nextProps.params.reportDetail.dataArea === "6"){
this.getCheckedDepList(); //获取 单条 报表副本 已选部门列表
}
});
this.setState({loading: false});
}
if (nextProps.params.newCopyReportDetail) {
this.setState({
loading: false,
isNew: nextProps.params.isNew,
newCopyReportDetail:nextProps.params.newCopyReportDetail
});
}
if (nextProps.params.distributionPeopleList) {
this.setState({
loading: false,
isNew: nextProps.params.isNew,
pagination: nextProps.params.pagination,
reportLineOID: nextProps.params.reportLineOID,
data: nextProps.params.distributionPeopleList
});
}
}
这里有一个小细节,因为更新state的时候数据必须分两次拿到,导致传入的props数据每次都有一个为空,如果取值时也一次性赋值,则会覆盖
2.页面之间如何传值,怎么拿到类似全局变量的一个值(react-redux相关)
这周的业务中有一个需求,是拿到当前的语言环境,是中文还是英文,显然,这应该是一个类似全局变量的值;另一个需求,是讲当前页active的tab值通过路由跳转后传到详情页。
上面是父子组件之间的传值,尚有一个公用的接口可以传递数据,那两个没有联系的页面或者说兄弟组件之间如何传值呢,要传值,就要找到联系他们的纽带,不难想象,这时候联系两个页面的纽带就是URL值或共同的父组件。这就引发了两个解决方法:context和React-Redux。
关于context,这篇文章说的很清楚React 组件通信之 React context
React是基于单向数据传递的,一般对于兄弟组件之间的通信,是通过它们共同的祖先组件进行的,即状态提升Lifting State Up,状态提升的意思是,当组件 A 需要依赖另外一个组件 B 的内部状态,而他们又不是父子关系时,需要将组件 B 的内部状态提升到他们公共的祖先组件中管理。这样他们就都可以通过属性接收到这份数据了。
当组件 B 需要对数据进行变更时,可以通过函数属性来通知祖先组件对数据更新,然后重新传递给子组件。
但但组件嵌套比较复杂的时候,这个方法着实很麻烦,context则能做到让组件树全局共享某状态。
context的使用方法也很简单,在顶部父组件声明context,那它的所有子组件可以通过 this.context 直接获取得到,项目业务中采用这个方式存取路由:
//顶部父组件
Main.childContextTypes = {
router: React.PropTypes.object
};
//子组件
ReportDistribution.contextTypes = {
router: React.PropTypes.object
};
关于React-Redux
首先,Redux 和 React-redux 并不是同一个东西。Redux 是一种架构模式(Flux 架构的一种变种),它不关注你到底用什么库,你可以把它应用到 React 和 Vue,甚至跟 jQuery 结合都没有问题。而 React-redux 就是把 Redux 这种架构模式和 React.js 结合起来的一个库,就是 Redux 架构在 React.js 中的体现。
参考文章:跟着例子一步步学习redux+react-redux
(这里没看完,待更新。。。)
另外还有一种解决方式:ref属性 ,这个和context一样是react官方不推荐使用的属性,因为可能会在未来的版本更新中被取消。
参考文章:React的Refs方法获取DOM实例 和 访问子组件方法及属性
3.如何触发render更新组件,如何避免不必要的render调用(理解生命周期
上面了解到,当state状态更新时,react会重发render,因此,在业务逻辑中常常采用子组件中componentWillReceiveProps接收props,更新state,触发重新渲染的方式更新视图,但是调试的输出的时候经常发现触发了不止一次,又或者当新建和不同表格行的编辑都共用一个组件时,没有正确的重发render导致表单无法重置。所以render和state的关系到底是什么??
这篇文章或许会有所帮助,【react】利用shouldComponentUpdate钩子函数优化react性能以及引入immutable库的必要性,由于业务有点复杂这里并没有来得及采用这个函数做优化了,但是也值得一看。
4.几个小tips
- 表达式优化
!!相当于Boolean()
eg:this.props.params.i ? true : false
写成!!this.props.params.id
比较好 - render函数代码中嵌套多个HTML标签,需要使用一个标签元素包裹他
(axios
react-redux
react-router
待更新。。。)
网友评论