React基础篇

93 阅读4分钟

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);
}

1644218876377.png

从以上结果,我们可以总结一下(重点):

  • 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实现组件的受控。

inputselecttextarea都是使用value传入

而对于checkboxradio则是使用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…

1644225419895.png