深入了解React组件-《React进阶之路》第四章读书笔记

深入了解React组件

《React进阶之路》第四章读书笔记

1.组件state

1.1设计合适的state

组件state必须能代表一个组件UI呈现的完整状态集,即组件的任何UI改变都可以从state的变化中反映出来;同时,state还必须代表一个组件UI呈现的最小状态集,即state中的所有状态都用来反映组件UI的变化,没有多余的状态,也不存在通过其它状态计算而来的中间件

//以下的state包含有个空数组,空数组的长度,以及数组中count的和,其中listCount和totalCount都不是必须的,可以省略
{
	list:[{count:0}],
	listCount:0,
	totalCount:0
}

state所代表的一个组件UI呈现的完整状态集又可以分为两类数据:
1)用作渲染组件时使用到的数据的来源(text);
2)用作组件UI展现形式的判断依据(isShow)

class C extends React.Companent{
	constructor(props){
		super(props);
		this.state = {
			text:"我是一段文字",
			isShow:true
		}
	},
	render(){
		return(
			<div>
				{this.state.isShow?<span>this.state.text</span>:null}
			</div>
		)
	}
}

当我们在组件中需要用到一个变量,并且它与组件的渲染无关时,就应该把这个变量定义为组件的普通属性1,直观的判断方法就是render()中有没有使用到这个变量,没有就是普通属性

class C extends React.Companent{
	constructor(props){
		super(props);
		this.timer = null;
		this.state = {
			date:new Date()
		}
		this.updateDate = this.updateDate.bind(this)
	},
	companentDidMount(){
		this.timer = setInterval(this.updateDate,1000)
	},
	companentWillUnMount(){
		clearInterval(this.timer)
	},
	updateDate(){
		this.setState({
			date:new Date()
		})
	},
	render(){
		return(
			<div>
				{this.state.date.toString()}
			</div>
		)
	}
}

state与props的区别:
props与state都直接与组件的UI渲染有关,它们的变化都会触发组件的重新渲染,但props对于使用它的组件来说是只读的,是通过父组件传递过来的,要想修改props,只能在父组件中修改;而state是组件内部自己维护的状态,是可变的。

组件中用到的变量是不是应该作为state的判断依据:
1)是否通过props获取?是则不是一个状态;
2)是否在组件的整个生命周期中都保持不变?是则不是一个状态;
3)是否可以通过其它状态或者属性计算得到?是则不是一个状态;
4)是否在组件的render()中使用?不是则不是一个状态,这种情况下更适合作为一个普通属性1

1.2正确修改state

1.直接修改state,组件不会重新出发render(),需要使用this.setState()
2.state的更新是异步的

调用setState时,组件的state不会立即改变,setState只是把要修改的状态放入一个队列之中,React会优化真正的执行时机,处于性能考虑,可能会将多次setState的状态修改合并成一次状态修改,props的更新也是异步的
比如连续调用两次this.setState({count:this.state.count + 1}),React会将多次修改合并为一次,等价于

Object.assign(
	previousState,
	{count:this.state.count+1},
	{count:this.state.count+1}
)

后面的操作会覆盖前面的,最终count只会+1
应该使用接收函数作为参数的setState

//争取写法
this.setState((preState,props)=>{
	count:preState.count+1
})
3.state的更新过程是一个合并的过程

当调用setState修改组件状态时,只需要传入发生改变的state,不需要完整的state,React会自动合并为新状态

this.state = {
	title:"标题1",
	content:"内容1"
}
this.setState({title:"标题2"})
//React会将title合并到state中
this.state = {
	title:"标题2",
	content:"内容1"
}

1.3state和不可变对象

React官方建议把state作为不可变对象,一方面this.state.{状态}不会重新render(),另一方面state中包含的状态应该是不可变对象。
原因:
一方面不需要担心原有对象在被不小心修改后导致的错误,方便程序的管理和调试;
一方面是为了性能考虑,当对象组件状态都为不可变对象,在组件的shouldCompanentUpdate方法中只需要比较前后两次状态对象的引用就可以判断状态是否真的被改变,从而避免不必要的render调用

当state的某个状态发生改变时,应该重新创建这个状态对象,而不是修改原来的状态,根据状态类型可以分为三种情况:
1)不可变类型(String、Number、Boolean、null、undefined)
因为状态为不可变类型,直接给修改的状态赋一个新值

this.setState({
	number:1,
	string:"String",
	boolean:true
})

2)数组
新增:concat()或者ES6的数组扩展语法

this.setState((preState,props)=>{
	list:preState.list.concat("React")
	//or
	list:[...preState.list,"React"]
})

截取:slice()

this.setState((preState,props)=>{
	list:preState.list.slice(0,1)
})

过滤:filter()

this.setState((preState,props)=>{
	list:preState.list.filter(item=>{
		return item !== "React"
	})
})

注意:不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为它们都是在原数组的基础上进行的修改,而concat、filter、slice会返回新的数组
3)普通对象(不包含字符串、数组)

this.setState((preState,props)=>{
	obj:Object.assign({},preState.obj,{name:"React"})
	//or
	obj:{...preState.obj,{name:"React"}}
})

创建新对象的关键是,避免使用会直接修改原对象的方法,而是使用可以返回一个新对象的方法

2.组件与服务器通信

只讨论组件从服务器上获取数据
React组件的正常运转本质上是组件不同生命周期的有序执行,因此组件与服务器的通信也必定依赖组件的生命周期方法

2.1组件挂载阶段通信

可以在companentWillMount和componentDidMount中执行服务器通信,两者的执行时间差微乎其微,完全可以忽略不计。
官方推荐在componentDidMount中进行服务器通信,原因:
1)可以保证当拿到数据时,组件已经处于挂载状态,这样即使操作DOM也是安全的,而componentWillMount无法保证
2)组件在服务器端渲染时,componentWillMount会被调用两次(一次在服务器端,另一次在用户端),而componentDidMount能保证在任何情况下只被调用一次,而不会发送多余的请求

而构造函数的意义是执行组件的初始化(如设置组件的初始状态),并不适合做数据请求这类的有“副作用”的工作,因此不推荐在构造函数中进行数据请求

2.2组件更新阶段通信

组件在更新阶段常常需要再次与组件进行通信,获取服务器上的最新数据。例如,父组件传入的props中的某个作为服务器数据请求参数的属性发生变化,组件需要重新与服务器进行通信,这个时候使用componentWillReceiveProps

componentWillReceiveProps(nextProps) {
	if(nextProps.{请求参数} !== this.props.{请求参数}) {
		this.setState(
			...
		)
	}
}

3.组件通信

一个React应用是许多组件像积木一样的搭建起来的,只要不是完全用展示组件组成,组件间就难免需要进行通信

3.1父子组件通信

通过props完成
1)父组件向子组件通信
父组件将数据通过props传递给子组件
2)子组件向父组件通信
父组件传递给子组件一个回调函数,子组件在需要改变父组件属性的时候,调用这个回调函数

3.2兄弟组件2通信

兄弟组件之间不可以直接进行数据通信,但是可以通过状态提升的方式实现兄弟组件的通信,即把组件之间需要共享的状态保存到距离他们最近的共同父组件内,任意一个兄弟组件都可以通过父组件传递的回调函数来修改共享状态,父组件中共享状态的变化也会通过props向下传递给所有兄弟组件,从而完成兄弟组件之间的通信

3.3context

React提供了一个context上下
文,让任意层级的子组件都可以获取父组件中的状态和方法
在提供context组件内新增一个getChildContext方法,返回context对象,然后在组件的childContextTypes属性上定义context对象的属性的类型信息

class UserListContainer extends React.Component{
	/** 省略其余代码 **/
	// 创建context对象,包含onAddUser方法
	getChildContext() {
		return {onAdd: this.handleAdd};
	}
	// 新增
	handleAdd(user) {
		this.setState((preState) => (
			{users:preState.users.concat([{'id':'c', 'name': 'cc' }])}
		))
	}
	render() {
		const filterUsers = this.state.users.filter((user) =>{user.id = this.state.currentUserId});
		const currentUser = filterUsers.length>0?filterUsers[0] : null;
		return (
			<UserList users={this.state.users} currentUserId = {this.state.currentUserId} onSetCurrentUser = {this.handleSetCurrentUser}/>
			<UserDetail currentUser = {currentUser} />
		)
	}
}
// 声明context的属性的类型信息
UserListContainer.childContextTypes = {
	onAdd: PropTypes.func
};

通过增加getChildContext和childContextTypes将onAdd在组件树中自动向下传递,当任意层级的子组件需要使用时,只需要在该组件的contextTypes 中声明使用的context属性即可

class UserAdd extends React.Component{
	/**省略其余代码**/
	handleChange(e) {
		this.setState({newUser: e.target.value});
	}
	handleClick() {
		if(this.state.newUser && this.state.newUser.length > 0){
			this.context.onAdd(this.state.newUser);
		}
	}
	render() {
		return (
			<div>
				<input onChange={this.handleChange} value={this.state.newUser} />
				<button onClick={this.handleClick}>Add</button>
			</div>
		)
	}
}
// 声明要使用的context对象的属性
UserAdd.contextTypes = {
	onAdd: PropTypes.func
};

3.4延伸

前三种组件通信方式都是依赖React组件自身的语法特性,还有更多的方式来实现组件通信
1)消息队列(EventEmitter或Postal.js):改变数据的组件发送消息,使用数据的组件监听消息,并在响应函数中触发setState来改变组件状态,本质上是观察者模式的体现
2)引用专门的状态管理库(Redux和MobX)实现组件通信和组件状态管理

4.组件ref属性

ref不仅可以在受控组件中用来获取表单元素,而且可以获取其他任意的DOM元素,甚至可以用来获取React组件实例。
在一些场景如控制元素的焦点、文本的选择或者和第三方操作DOM库集成,中带来便利。
但在绝大多数场景下,应该避免使用ref,因为它破坏了React中以props为数据传递介质的典型数据流

4.1在DOM元素上使用ref

ref接收一个回调函数作为值,当组件挂载或卸载时,回调函数被调用:
在组件挂载时,回调函数会接收当前DOM元素作为参数
在组件卸载时,回调函数会接收null作为参数

class AutoFocusTextInput extends React.Component {
	componentDidMount() {
		// 通过ref让input自动获取焦点
		this.textInput.focus();
	}
	render() {
		return (
			<div>
				<input type="text" ref={(input) => { this.textInput = input; }} />
			</div>
		)
	}
}

4.2在组件上使用ref

ref的回调函数接收的参数是当前组件的实例,提供了一种在组件外部操作组件的方式
注意:只能为类组件定义ref属性

class AutoFocusTextInput extends React.Component {
	constructor(props) {
		super(props);
		this.blur = this.blur.bind(this);
	}
	componentDidMount() {
		// 通过ref让input自动获取焦点
		this.textInput.focus();
	}
	// 让input失去焦点
	blur() {
		this.textInput.blur();
	}
	render() {
		return (
			<div>
				<input type="text" ref={(input) => { this.textInput = input; }} />
			</div>
		);
	}
}
class Container extends React.Component {
	constructor(props) {
		super(props);
		this.handleClick = this.handleClick.bind(this);
	}
	handleClick() {
		// 通过ref调用AutoFocusTextInput组件的方法
		this.inputInstance.blur();
	}
	render() {
		return (
			<div>
				<AutoFocusTextInput ref={(input) =>{this.inputInstance = input}}/>
				<button onClick={this.handleClick}>失去焦点</button>
			</div>
		);
	}
}

4.3父组件访问子组件的DOM节点

使用回调函数和ref:在子组件的DOM元素上定义ref,ref的值是父组件传递给子组件的一个回调函数,这样父组件的回调函数中就可以获取到这个DOM元素
即使子组件是函数组件,也可以获取

function Children(props) {
	// 子组件使用父组件传递的inputRef,为input的ref赋值
	return (
		<div>
			<input ref={props.inputRef} />
		</div>
	);
}
class Parent extends React.Component {
	render() {
		// 自定义一个属性inputRef,值是一个函数
		return (
			<Children inputRef={el => this.inputElement = el} />
		);
	}
}

  1. 组件的普通属性,除了props、state以外的其他组件属性
    1)ES6中,属性使用this.{属性名}来定义,是直接挂载到this下的变量
    2)props和state实际上也是组件的属性,只不过它们是React在Companent类中预定义好的属性 ↩︎ ↩︎

  2. 不是父子关系,但是拥有相同父组件的组件 ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值