react 学习笔记一

1. setState

setState 的两种写法

(1). setState(stateChange,[callback]) ---- 对象式的setState
    1. stateChange 为状态改变的对象 `{}`
    2. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.

(2). setState(updater,[callback]) ---- 函数式的setState
    1. updater 为返回 stateChange 的函数 .
    2. updater 可以接受两参数 prevState , props
    3. callback 是可选参数,会在状态改变以及render函数调用完毕之后执行.
    
总结: 
    1. 对象式setState就是函数式的语法糖.(在内部会定义一个函数,返回stateChange对象)
    2. 使用原则:
        1. 如果修改的状态不依赖原来的状态,使用对象式setState
        2. 如果修改的状态依赖于原状态,使用函数式setState
        3. 如果需要在setState之后,拿到最新的状态,则需要使用可选参数 callback.

代码例子:

import React, { Component } from 'react'

export default class Demo extends Component {
  state = {
    count: 1
  }

  add = () => {
    //#region 对象式的setState
    // 在原来的对象上修改,也可以修改状态
    // this.state.count++
    // this.setState(this.state)

    // 第一种setState的方法
    // this.setState({ count: this.state.count + 1 })
    // console.log(this.state.count) // 1 setState 修改也是异步的,所以这里返回的是1
    // 如果你需要使用最新的state状态,在setState的第二个回调函数里就可以拿到(在state和界面更新之后调用)
    // this.setState({ count: this.state.count + 1 }, () => {
    //   console.log(this.state.count) // 2
    // })
    // this.setState({ count: this.state.count + 1 })
    // setTimeout(() => {
    //   console.log(this.state.count);
    // }, 0);
    //#endregion

    //#region 函数式的setState
    console.log('setState');
    this.setState((prevState, props) => {
      console.log(props);
      return { count: prevState.count + 1 }
    }, () => {
      // 在状态更新之后执行render之后执行
      console.log('callback');
      console.log(this.state.count)
    })
    //#endregion
  }
  render() {
    console.log('render');
    const { count } = this.state
    return (
      

当前计数为: {count}

) } }

2. 路由组件的 lazyLoad

路由组件的lazyLoad

// 1. 通过React的lazy函数配合 import()函数动态加载路由组件 ====> 路由组件的代码会单独打包
import { lazy } from 'react'
const Login = lazy(()=>import('@/pages/login'))

// 2. 通过  指定在加到得到的路由打包组件加载完毕之后,自由的显示一个Loading组件
render () {
    return (
        }>
            
                
            
        
    )
}

3. Hooks

React Hook是什么?

  1. Hook 是 react 16.8.0 版本中新增加的新特性/新语法.
  2. 可以让你在函数式组件中,使用class组件的其他特性(比如,state,生命周期等)

三个常用的hook

  1. React.useState()
  2. React.useEffect()
  3. React.useRef()

React.useState

  1. React.useState 可以让函数式组件,也可以用于类似于class组件的 state 状态.
  2. 语法 const [xxx,setXXX] = React.useState(initValue)
  3. React.useState参数和返回值说明
    1. 参数 initValue 就是当前状态的初始值.
    2. 返回值:
      1. 返回值是一个数组 [xxx,setXXX]
      2. 第一个xxx,就是state的值.
      3. 第二个参数是用于修改xxx为新状态的函数.
  4. setValue的两种用户
    1. setXXX(newVal):参数为非函数,直接用新的状态覆盖原来旧的状态.
    2. setXXX(prevState=>newState): 参数为函数,函数接受上一次的状态,在此函数内部返回一个新的状态覆盖原来旧的状态.

演示代码:

import React, { useState } from 'react';


function Demo(props) {
  // 让函数式组件可以拥有自己的状态,第一次调用此函数组件是,react内部会将count缓存,后期再次调用此组件时,用的是缓存的值.
  const [count, setCount] = useState(0)
  const [name, setName] = useState('jack')

  // 点我加1
  const add = () => {
    // setCount(count + 1)
    setCount((count) => count + 1)
    // console.log('newCount:', count); // 同步无法拿到最新的count
  }

  const change = () => {
    setName(name => name += "gqs")
  }

  return (
    

当前求和为:{count}

当前的名字:{name}

); } export default Demo;

React.useEffect

  1. React.useEffect 可以让你在函数式组件中,模拟class组件的生命周期钩子函数.(所谓的副作用操作)
  2. React中有哪些副作用操作?
    1. 异步请求
    2. 设置发布订阅/启动定时器
    3. 手动更改真实的DOM.
  3. 语法和说明
    // 等价于 class 组件的 componentDidUpdate 钩子
    useEffect(()=>{
        console.log('当任意state发生改变时,都会触发-componentDidUpdate') // 监听所有的属性发生改变时,都会出发此函数,等价于 componentDidUpdate
    })
    
    // 等价于 class 组件的 componentDidMount 钩子
    useEffect(()=>{
        console.log('componentDidMount') 
    },[]) // 参数传空数组,不监听任何state的change,等价于 componentDidMount
    
    // 等价于 class 组件的 componentDidUpdate ,仅针对监听的state
    useEffect(()=>{
        console.log('当count的state放生改变时触发 - componentDidUpdate')
    },[count])
    
    // 等价于 class 组件的 componentWillUnmount 
    useEffect(()=>{
        return () => {
            console.log('componentWillUnmount')
        }
    },[])
    
    
  4. 可以将 useEffect 加上参数组合的方式,来实现以下三种生命周期钩子效果.
    1. componentDidUpdate - 监听所有属性,或者某些属性
    2. componentWillUnmount - 当函数式组件销毁时
    3. componentDidMount - 不监听任意属性.
useEffect模拟三种事件钩子

React.useRef

  1. React.useRef 可以在函数式组件中存储/查找组件内标签或任意其他数据
  2. 语法: const ref = React.useRef(), 然后再你需要引用的组件或者dom元素上使用 ref={ref} 即可.
  3. 作用: 用于保存标签对象,功能于 React.createRef 一致.
  4. useRef 既可以引用DOM 也可以引用组件实例.
import React, { useRef } from 'react';
import Test from './test';

function Demo(props) {
  // 声明一个input的引用
  const inputRef = useRef()
  // 能引用一个组件吗? -> 结论,可以
  const testRef = useRef()
  const show = () => {
    // alert(document.getElementById('abc').value)
    alert(inputRef.current.value)
  }

  const test = () => {
    console.log(testRef.current)
    testRef.current.show()
  }

  return (
    
{/* 这里是引用一个组件 */}
); } export default Demo;

Fragment


<>

作用:

可以不必拥有一个真实的dom.用上述两种标签占位!


createContext

一种祖孙组件间的通信方式,类似于Vue的 provider/inject .

使用方式:

1. 创建context容器对象
    const Context = React.createContext()
2. 渲染子组件时,外面包裹 , 并通过value属性,将需要传递给后代组件.
    
        
    
3. Other 组件以及后面的所有组件,就都可以通过某些设置,拿到value的值.
4. 后代组件读取数据.
    1. class 组件 (仅用于class组件)
        static contextType = Context
        this.context = 就能拿到value的值.
    2. 函数式组件 (函数式组件和类式组件都可以)
        
            {
                (value) => {
                    // value 就是context提供的值.
                    return 
  • {value.name} - {value.age}
  • } }

    演示代码如下:

    import React, { Component } from 'react'
    import './index.scss'
    
    // 创建context上下文组件
    const NameContext = React.createContext()
    
    export default class A extends Component {
      state = { name: 'tom' }
      render() {
        return (
          

    我是A组件

    我的用户名是:{this.state.name}

    {/* 第一步 */}
    ) } } class B extends Component { // 第二步,声明接受contextType static contextType = NameContext render() { return (

    我是B组件

    {/* 父子组件间可以使用props传递 */} {/*

    我从A组件拿到的用户名是:{this.props.name}

    */}

    我从A组件拿到的用户名是:{this.context.name}

    ) } } class C extends Component { // 第二步,声明接受contextType static contextType = NameContext render() { // console.log(this); return (

    我是C组件

    我从A组件拿到的用户名是:{this.context.name}

    ) } } // 函数式组件 function D() { return (

    我是D组件

    我从A组件拿到的用户名是: { value => { return `${value.name}` } }

    ) }

    组件渲染优化

    React.Component 的 2 个问题

    1. 只要执行了setState,发生了状态改变,不论state状态是否在模板上显示,都会重新调用 render 函数.但组件效率低.
    2. 如果父组件重新render了,那么子组件也会被render,多组件效率低下.

    效率高的做法

    只有当依赖的state或者props,在视图中显示时,才调用render函数.

    问题核心:

    React 中,你只要调用了 setState 就会重新render,因为 shouldComponentUpdate() 钩子函数总是返回true.

    解决办法:

    1. 重写 shouldComponentUpdate,比较 stateprops 来决定是否调用 render 渲染函数.
    2. 使用 PureComponent
      PureComponent 重写了 shouldComponentUpdate, 只有当 state 或者 props 有变化时才返回为 true
      注意: 只是进行 stateprops 的浅层比较. 如果只是对象内部的数据发生了变化,直接返回false.你必须在setState里传递一个新的对象
      项目中,一般使用 PureComponent 用来进行渲染性能优化.

    演示代码:

    import React, { Component } from 'react'
    
    // // 测试代码一
    // export default class Demo extends Component {
    //   update = () => {
    //     this.setState({})
    //   }
    //   render() {
    //     console.log('React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true')
    //     return (
    //       
    //

    React render 很蠢,只要你调用了setState,而不管状态是否依赖在视图上,都会执行render函数!原因是 componentShouldUpdate 默认总是返回true

    // //
    // ) // } // } // 测试代码二,自己来优化,只有当props和state发生改变时,才更新视图,重新调用render 函数 // export default class Demo extends Component { // state = { // username: '张三' // } // changeName = () => { // this.setState({ username: '李四' }) // // this.setState({}) 如果传空对象,那么 nextState 和 this.state 时同一个对象. // // username:undefined // } // /** // * // * @param {*} newProps 更新后的 props // * @param {*} newState 更新后的 state // */ // shouldComponentUpdate(nextProps, nextState) { // // console.log(this.state, nextState); // const oldStateStr = JSON.stringify(this.state) // const newStateStr = JSON.stringify(nextState) // const oldPropsStr = JSON.stringify(this.props) // const newPropsStr = JSON.stringify(nextProps) // // 当新旧props和state发生变化时,才需要render. // return oldStateStr !== newStateStr || oldPropsStr !== newPropsStr // } // render() { // console.log(`render`); // const { username } = this.state // return ( //
    //

    用户的名字是:{username}

    // // {/* 没依赖父组件的数据,没接受props, 就没有必要render */} // //
    // ) // } // } // class Child extends React.Component { // // 由于默认情况下,父组件render 会导致子组件render.所以,对于某些静态组件,可以直接返回false,不需要render // shouldComponentUpdate(nextProps, nextState) { // // 对于纯展示的静态组件,可以直接返回false // return false // } // render() { // console.log('child-render'); // return ( //

    纯静态组件,无需调用render

    // ) // } // } // 测试代码三,使用 PureComponent // PureComponent 自动帮我们实现了 shouldComponentUpdate 函数,但是只有一层比较. export default class Demo extends React.PureComponent { state = { name: '李四', age: 22, // 浅层 info: { address: '湖北', // 深层 zicode: 43333 } } changeName = () => { this.setState({ name: '张三' }) // const obj = this.state // obj.name = '李四' // this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码 /** * setState (stateChange) { * if (orginState === stateChange) return * } * */ } changeAge = () => { this.setState({ age: 33 }) } changeAddress = () => { // 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render this.setState({ info: { address: '武汉' } }) } changeZiCode = () => { // 对于大于一层属性的修改,PureComponent 不会进行对比前后是否相等,而是直接调用render this.setState({ info: { zicode: 123456 } }) } render() { console.log('render'); const { name, age, info: { address, zicode } } = this.state return (

    name(浅层属性):{name}

    age:(浅层属性):{age}

    address:(深层属性):{address}

    zicode:(深层属性):{zicode}

    ) } }

    使用 PureComponent 的注意点:

    必须给setState传递一个新的对象,否则 setState 不做任何操作!

     // const obj = this.state
        // obj.name = '李四'
        // this.setState(obj) // 注意,当你继承了PureComponent,那么 setState 里必须传递一个新的对象!否则,setState 函数内部拒绝执行之后的代码 
        /**
         * setState (stateChange) {
         *  if (orginState === stateChange) return 
         * }
         * 
         */
    

    render props

    如何在 React 里实现类似 Vue 插槽的技术?

    1. 使用 render props

    请查看代码

    import React, { PureComponent } from 'react'
    import './index.scss'
    export default class Demo extends PureComponent {
    
      render() {
        return (
          

    我是最外层组件

    {/* 给Child组件传递了一个render属性,是一个函数,函数返回一个组件实例 */} ()} />
    ) } } class Child extends PureComponent { state = { name: 'Hello World In Child Component' } render() { return (

    我是Child组件,我的state值是:{this.state.name}

    {/* 拿到props里的render,然后去调用这个实例 第一种实现插槽的方式 */} {this.props.render(this.state.name)}
    ); } } function Demo2(props) { return (

    我是Demo2函数式组件,我由Child组件渲染.

    收到来自Child组件的数据:{props.name}

    ) }
    1. 使用 this.props.children

    查看代码:

    class Child2 extends React.PureComponent {
      render() {
        return (
          

    我是Child2组件

    {/* 第二种实现插槽的方式 */} {this.props.children}
    ) } } function Demo2(props) { console.log(props); return (

    我是Demo2函数式组件,我由Child组件渲染.

    收到来自Child组件的数据:{props.name}

    ) }

    错误边界

    理解:

    错误便捷(Error boundary);用来解决后代组件的错误,渲染出备用页面,不至于内部的某个组件发生错误,导致整个程序全部崩溃!

    特点:

    只能捕获后代组件[生命周期里]发生的错误,不能捕获自身由于用于操作,定时器,以及合成事件里的错误! 你可以理解为,只能捕获组件第一次初始化渲染时候发生的错误!

    演示代码:

    import React, { PureComponent } from 'react'
    import './index.scss'
    
    export default class Parent extends PureComponent {
      state = {
        hasError: '' // 用于标识子组件是否发生错误?
      }
      // 当 parent的所有后代出现error时,会出发此函数的调用,并携带error参数
      static getDerivedStateFromError(error) {
        console.log(error)
        return {
          hasError: error
        }
      }
    
      componentDidCatch(error) {
        // 可以收集到错误,并发送给后台统计.
        console.log("&&&", error)
      }
    
      render() {
        return (
          

    我是Parent组件组件

    {this.state.hasError ?

    当前网络不稳定,请稍后再试....

    : }
    ) } } class Child extends PureComponent { state = { users: undefined } happen = () => { // 这种错误无法捕获,只有在生命周期里的错误才能捕获! throw new Error('在child组件里发生了一个错误!') } render() { return (

    我是Child组件

      {this.state.users.map(user => (
    • {user.name}
    • ))}
    ) } }

    组件间通信的方式

    组件间的关系:

    1. 父子组件
    2. 兄弟组件(非嵌套关系)
    3. 祖孙组件

    集中通信方式:

    1. props
      • 父 -> 子 props.everyThing 子 -> 父 props.function
      • render props 插槽.
    2. 消息发布订阅
      • pubsub / eventBus 等.
    3. 状态集中式管理
      • redux / dva 等等
    4. context
      • 类似 Vue 的 Provider / inject

    你可能感兴趣的:(react 学习笔记一)