React
事件的触发和Event
在react中定义事件可以有如下两种方式:
普通函数
constructor(props) {
super(props)
this.state = {
...
}
// 普通写法bind必不可少
this.clickHander = this.clickHander.bind(this)
}
clickHander() {
this.setState({
name: 'Air'
})
}
箭头函数
// this指向当前实例 无需bind绑定
clickHander = () => {
this.setState({
name: 'Air'
})
}
当然你也可以在函数调用时进行绑定this,或者使用箭头函数调用普通函数来解决这个问题
Event的不同
再来看看react中event的不同:
clickHander = (event) => {
event.preventDefault();
event.stopPropagation();
console.log('target', event.target);
console.log('current target', event.currentTarget);
console.log('event', event);
// console.log('event.__proto__.constructor', event.__proto__.constructor);
console.log('nativeEvent', event.nativeEvent);
console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发
console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 root !!!
// console.log('nativeEvent.__proto__.constructor', event.nativeEvent.__proto__.constructor);
}
从以上结果,我们可以总结一下(重点):
- react的event是SyntheticEvent ,模拟出来 DOM 事件所有能力
- event.nativeEvent 是原生事件对象,其 _proto_.constructor 是 PointerEvent
- 所有事件都被挂载到了root节点上(react17前是document,后面为了不污染document)
- 和 DOM 事件不一样,和 Vue 事件也不一样
表单-受控组件
回忆一下在Vue中我们写表单通常使用的v-model,那么在react中怎么可以实现类似的“双向绑定”呢?我们可以把这个元素写成受控组件。
先来看个例子理解一下:
import React, { Component } from 'react';
export default class FormDemo extends Component {
constructor(props) {
super(props)
this.state = {
name: 'airhua',
info: '个人信息',
gender: 'male'
}
}
onTextareaChange = (e) => {
this.setState({
info: e.target.value
})
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
})
}
render() {
return <div>
<p>{ this.state.name }</p>
<p>{this.state.info}</p>
<textarea value={this.state.info} cols="30" rows="10" onChange={this.onTextareaChange}></textarea>
<hr />
<p>{this.state.gender}</p>
男 <input type="radio" name="gender" value="male" checked={ this.state.gender === 'male' } onChange={this.onRadioChange} />
女 <input type="radio" name="gender" value="female" checked={ this.state.gender === 'female' } onChange={this.onRadioChange} />
</div>;
}
}
在html中,textarea
元素通过定义其子元素定义其文本:
<textarea>
你好, 这是在 text area 里的文本
</textarea>
而在 React 中,textarea
使用 value
属性代替。这样,可以使得使用 textarea
的表单和使用单行 input 的表单非常类似。总体来说就是value+onChange
实现组件的受控。
input
、select
、textarea
都是使用value传入
而对于checkbox
和radio
则是使用checked
父子组件传值
通过一个TodoList案例来看
import React, { Component } from 'react';
import PropTypes from 'prop-types'
// 子组件1
class Input extends Component {
constructor(props) {
super(props)
this.state = {
task: ''
}
}
taskChange = (e) => {
this.setState({
task: e.target.value
})
}
submit = () => {
const { submitTitle } = this.props
submitTitle(this.state.task)
}
render() {
return <div>
<input type="text" value={this.state.task} onChange={this.taskChange} />
<button onClick={this.submit}>提交</button>
</div>;
}
}
// 设置默认类型1
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
}
// 子组件2
class List extends Component {
// 设置默认值2
static defaultProps = {
list: [
{
createTime: '19: 00',
title: '摆烂'
}
]
}
static propTypes = {
list: PropTypes.arrayOf(PropTypes.object)
}
constructor(props) {
super(props)
this.state = {}
}
render() {
return <div>
<ul>
{
this.props.list.map((item, index) => (
<li key={item.createTime}>{ item.title }</li>
))
}
</ul>
</div>;
}
}
// 子组件3
class Footer extends Component {
constructor(props) {
super(props)
this.state = {}
}
render() {
return <div>
<p>待做事项: { this.props.length }</p>
</div>;
}
}
// 父组件
export default class PropsDemo extends Component {
constructor(props) {
super(props)
this.state = {
list: [
{
createTime: '2022-01-01 8:00',
title: '完成跑步'
},
{
createTime: '2022-01-01 8:30',
title: '吃饭'
},
{
createTime: '2022-01-02 22:00',
title: '看书'
}
]
}
}
submitTitle = (title) => {
this.setState({
list: this.state.list.concat({
createTime: new Date(),
title: title
})
})
}
render() {
return <div>
<Input submitTitle={this.submitTitle}></Input>
<List list={this.state.list}></List>
<Footer length={this.state.list.length}></Footer>
</div>;
}
}
setState
三个特性(重点)
- 不可变值
- 可能是异步,可能是同步
- 直接写是异步
- setTimeout和自定义DOM事件等中为同步
- 多个setState可能会被合并
- 传入的是对象会被合并
- 传入的是函数不被合并
不可变值
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
list5: list5Copy // 其他操作
})
/*
注意,不能直接对 this.state.list 进行 push pop splice 等,这样直接修改的是原数组,违反不可变值定义
*/
注意观察以上更新state的操作,不知道你有没有发现这里更新传入的值都是一个新的数组,而不是直接修改state里面的数组,因为state里面的值只能通过setState修改,这就是不可变值定义。
setState里面的同步与异步
- 直接后面写是异步
- setTimeout和自定义DOM事件等中为同步
this.setState({
count: this.state.count + 1
}, () => {
// 联想到 Vue中 $nextTick - DOM
console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值
// setTimeout 中 setState 是同步的
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('count in setTimeout', this.state.count)
}, 0)
多个setState可能会被合并
// 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
// 传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
关于setState为什么会有这些特性,我们后面的博客会讲到。
React组件生命周期
这里放一张图,写明了详细的生命周期,来源网站:projects.wojtekmaj.pl/react-lifec…