react学习记录(二)

105 阅读8分钟

核心概念

生命周期

(旧)--16

初始化阶段

由ReactDOM.render()触发--初次渲染

  • constructor
  • componentWillMount:render之前最后一次修改状态的机会
  • render:只能访问this.props和this.state,不允许修改状态和DOM输出
  • componentDidMount:组件装载之后调用,此时可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在componentWillUnmount中取消订阅

更新阶段

由组件内部this.setState()或父组件重新render触发

  • componentWillReceiveProps:父组件修改属性触发改钩子
  • shouldComponentUpdate:返回false会阻止render调用
  • componentWillUpdate:不能修改属性和状态
  • render:只能访问this.props和this.state,不允许修改状态和DOM输出
  • componentDidUpdate:在组件重新渲染后调用该方法。componentDidUpdate(prevProps, prevState, snapshot),该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevPropsprevStatesnapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的,如果触发某些回调函数时需要用到 DOM 元素的状态,则将对比或计算的过程迁移至getSnapshotBeforeUpdate,然后在 componentDidUpdate中统一触发回调或更新状态。

卸载阶段

由ReactDOM.unmountComponentAtNode()触发

  • componentWillUnmount:当组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作
class Count extends React.Component {

  // 构造器
  constructor(props) {
    console.log('Count-constructor')
    super(props)
    // 初始化状态
    this.state = {
      count: 0
    }
  }


  // +1按钮的回调
  add = () => {
    // 获取原状态
    const { count } = this.state
    // 更新状态
    this.setState({ count: count + 1 })
  }

  // 卸载组件按钮的回调 
  death = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('test'))
  }

  // 强制更新按钮的回调
  force = () => {
    this.forceUpdate()
  }

  // 组件将要挂载的钩子
  componentWillMount() {
    console.log('Count--componentWillMount')
  }

  // 组件挂载完毕的钩子
  componentDidMount() {
    console.log('Count--componentDidMount')
  }

  // 组件将要卸载的钩子
  componentWillUnmount() {
    console.log('Count--componentWillUnmount')
  }

  // 控制组件更新的阀门
  shouldComponentUpdate() {
    console.log('Count--shouldComponentUpdate')
    return true
  }

  // 组件将要更新的钩子
  componentWillUpdate() {
    console.log('Count--componentWillUpdate')
  }

  // 组件更新完毕的钩子
  componentDidUpdate() {
    console.log('Count--componentDidUpdate')
  }

  render() {
    console.log('Count--render')
    const { count } = this.state
    return (
      <div>
        <h2>当前求和为{ count }</h2>
        <button onClick={ this.add }>点击+1</button>
        <button onClick={ this.death }>卸载组件</button>
        <button onClick={ this.force }>不更改任何状态中的数据,强制更新一下</button>
      </div>
    )
  }
}

// 父组件A
class A extends React.Component {
  // 初始化状态
  state = { carName: '奔驰' }

  changeCar = () => {
    this.setState({ carName: '奥迪' })
  }
  render() {
    return(
      <div>
        <div>A组件</div>
        <button onClick={this.changeCar}>换车</button>
        <B carName={this.state.carName} />
      </div>
    )
  }
}
// 子组件B
class B extends React.Component {

  // 第一次不算---组件将要接收新的props的钩子
  componentWillReceiveProps(props) {
    console.log('B --componentWillReceiveProps', props)
  }
  // 控制组件更新的阀门
  shouldComponentUpdate() {
    console.log('B--shouldComponentUpdate')
    return true
  }

  // 组件将要更新的钩子
  componentWillUpdate() {
    console.log('B--componentWillUpdate')
  }

  // 组件更新完毕的钩子
  componentDidUpdate() {
    console.log('B--componentDidUpdate')
  }

  render() {
    console.log('B--render')
    return(
      <div>B组件,接收到的车是:{this.props.carName}</div>
    )
  }
}
ReactDOM.render(<A />, document.getElementById('test'))

新--17

初始化阶段

  • constructor()
  • getDerivedStateFromProps
  • render()
  • componentDidMount()

更新阶段

  • getDerivedStateFromProps
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate
  • componentDidUpdate()

卸载组件

  • componentWillUnmount()

组件通信

react组件之间的通信常见的方式:

  • 父向子通信
  • 子向父通信
  • 跨级组件通信
  • 非嵌套关系的组件通信
  1. 父向子通信 父组件通过 props 向子组件传递需要的信息。父传子是在父组件中直接绑定一个正常的属性,这个属性就是指具体的值,在子组件中,用props就可以获取到这个值。
// 子组件
const Child = props =>{ return <p>{props.name}</p> }
// 父组件 
Parent const Parent = ()=>{ return <Child name="父向子通信"></Child> }
  1. 子向父通信 props+回调的方式,使用公共组件进行状态提升。子传父是先在父组件上绑定属性设置为一个函数,当子组件需要给父组件传值的时候,则通过props调用该函数将参数传入到该函数当中,此时就可以在父组件中的函数中接收到该参数了,这个参数则为子组件传过来的值。
// 子组件 Child
const Child = props => {
    const cs = msg => {
        return () => {
            props.callback(msg)
        }
    }
    return (
        <button onClick={cs('子向父通信')}>点击</button>
    )
}
// 父组件 Parent
class Parent extends Component {
    callback(msg) {
        console.log(msg)
    }
    render() {
        return <Child callback={this.callback.bind(this)}/>
    }
}
  1. 跨级组件通信 如父组件向子组件的子组件或更深层次的子组件进行通信
  • 使用props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递props,增加了复杂度,并且这些props并不是中间组件自己需要的。

  • 使用context,context相当于一个大容器,我们可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用context实现。

// context方式实现跨级组件通信
// Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据
const BatteryContext = createContext();
// 子组件的子组件
class GrandChild extends Component {
    render(){
        return (
            <BatteryContext.Consumer>
                {
                    color => <h1 style={{"color":color}}>我是红色的:{color}</h1>
                }
           </BatteryContext.Consumer>
        ) 
    }
}
// 子组件
const Child = () =>{
    return (
        <GrandChild/>
    )
}
// 父组件
class Parent extends Component {
    state = {
        color:"red"
    }
    render(){
        const {color} = this.state
        return (
            <BatteryContext.Provider value={color}>
                <Child></Child>
            </BatteryContext.Provider>
        )
    }
}
  1. 非嵌套关系的组件通信 即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。
  • 可以使用自定义事件通信(发布订阅模式),使用pubsub-js
  • 可以通过redux等进行全局状态管理
  • 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
  • 也可以new一个 Vue 的 EventBus,进行事件监听,一边执行监听,一边执行新增 VUE的eventBus 就是发布订阅模式,是可以在React中使用的;

受控组件、非受控组件

受控组件

  • HTML中的表单元素是可输入的,也就是有自己的可变状态,而React中可变状态通常保存在state中,并且只能通过setState()方法来修改
  • React讲state与表单元素值value绑定在一起,有state的值来控制表单元素的值
  • 受控组件:值受到react控制的表单元素
// 页面中所有输入类的DOM是随着输入维护状态--受控组件
class Login extends React.Component{

  // 初始化状态
  state = {
    username: '',
    password: ''
  }

  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault() // 阻止表单提交
    const { username, password } = this.state
    alert(`你输入的用户名是${username},密码是${password}`)
  }

  // 保存用户名到状态中
  saveUsername = (event) => {
    console.log(event.target.value)
    this.setState({ username: event.target.value })
  }

  savePassword = (event) => {
    this.setState({ password: event.target.value })
  }

  render() {
    return (
      <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveUsername} type="text" name="username" />
        密码:<input onChange={this.savePassword} type="password" name="password" />
        <button>登录</button>
      </form>
    )
  }
}
ReactDOM.render(<Login />, document.getElementById('test'))

非受控组件

  • 调用React.createRef()方法创建ref对象,将创建好的ref对象添加到文本框中,通过ref对象获取文本框的值
  • 非受控组件:表单组件没有 value prop就可以称为非受控组件
// 页面中所有输入类的DOM是现用现取--非受控组件
class Login extends React.Component{
  handleSubmit = (event) => {
    event.preventDefault() // 阻止表单提交
    const { username, password } = this
    alert(`你输入的用户名是${username.value},密码是${password.value}`)
  }
  render() {
    return (
      <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
        用户名:<input ref={ c => this.username = c } type="text" name="username" />
        密码:<input ref={ c => this.password = c } type="password" name="password" />
        <button>登录</button>
      </form>
    )
  }
}
ReactDOM.render(<Login />, document.getElementById('test'))

高阶组件

高阶函数与科里化

高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
1、若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
2、若A函数,调用的返回值依然是一个函数,那么A函数就可以称之为高阶函数
常见的高阶函数:Promise、setTimeout、数组方法(arr.map()等等)

函数科里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。

class Login extends React.Component{

  // 初始化状态
  state = {
    username: '',
    password: ''
  }

  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault() // 阻止表单提交
    const { username, password } = this.state
    alert(`你输入的用户名是${username},密码是${password}`)
  }

  // 保存参数到状态中
  saveFormData = (dataType) => {
    return (event) => {
      /*
        let a = 'name'
        let obj = {} // { name: 'Tome' }
        obj[a] = 'Tom'
      */
      this.setState({ [dataType]: event.target.value })
    }
  }

  render() {
    return (
      <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
        用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />
        密码:<input onChange={this.saveFormData('password')} type="password" name="password" />
        <button>登录</button>
      </form>
    )
  }
}
ReactDOM.render(<Login />, document.getElementById('test'))

不用科里化实现上述功能:

class Login extends React.Component{

  // 初始化状态
  state = {
    username: '',
    password: ''
  }

  // 表单提交的回调
  handleSubmit = (event) => {
    event.preventDefault() // 阻止表单提交
    const { username, password } = this.state
    alert(`你输入的用户名是${username},密码是${password}`)
  }

  // 保存参数到状态中
  saveFormData = (dataType,event) => {
    this.setState({ [dataType]: event.target.value })
  }

  render() {
    return (
      <form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
        用户名:<input onChange={event => this.saveFormData('username',event)} type="text" name="username" />
        密码:<input onChange={event => this.saveFormData('password',event)} type="password" name="password" />
        <button>登录</button>
      </form>
    )
  }
}
ReactDOM.render(<Login />, document.getElementById('test'))

diff算法

image.png

1、react/vue中key有什么作用?(key的内部原理是什么?)
    虚拟DOM中key的作用:
        1)、简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用
        2)、详细的说:当状态中的数据发生变化的时候,react会根据【新数据】生成【新的虚拟DOM】,随后react进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
          a、旧虚拟DOM中找到了与新虚拟DOM相同的key:
              若虚拟DOM中内容没有变,直接使用之前的真实DOM;
              若虚拟DOM中的内容变了,则生成新的虚拟DOM,随后替换掉页面中之前的真实DOM。
          b、旧虚拟DOM中未找到与新虚拟DOM相同的key
              根据数据创建新的真实DOM,随后渲染到页面
      
      
2、为什么遍历列表时,key最好不要用index?
    用index作为key可能会引发的问题:
      1)、若对数据进行:逆序添加、逆序删除等破坏顺序操作
            会产生没有必要的真实DOM更新 ==> 界面效果没有问题,但效率低
      2)、如果结果中还包含输入类的DOM:
            会产生错误DOM更新 ==> 界面有问题
 
 
3、开发中如何选择key1)、最好使用每条数据的唯一标识作为key,比如:id、手机号、身份证号、学号等唯一值
      2)、如果确定只是简单的展示数据,用index也是可以的