react基础05-setState的两种方式以及回调函数、路由组件懒加载、hooks、Fragment、context、shouldComponentUpdate和PureComponent进行组件优化、children props和render props、错误边界、react组件通信总结

setState()

  1、setState有两种写法

    第一种:对象形式

            this.setState({ count: count + 1 }, () => console.log(this.state.count))

    第二种:函数形式,返回一个对象。函数的第一个参数是state,第二个参数是props

            this.setState(
              (state, props) => ({ count: state.count + 1 }),
              () => console.log(this.state.count)
            )

  2、setState还有第二个参数,callback,是一个回调函数,可选,它在状态更新完后、视图也更新(render)完后才被调用

    如果需要在setState执行后获取最新的状态,就在callback中获取,有点像this.$nextTick()

 

路由组件懒加载

  1、通过lazy方法和import动态加载路由组件

  2、通过<Suspense></Suspense>指定在加载得到路由打包文件前显示一个loading界面

import React, { Component, lazy, Suspense } from 'react'
import { NavLink, Route, Switch, Redirect } from 'react-router-dom'
import Loading from './Loading'
// import About from './About'
// import Home from './Home'
// import Home1 from './Home1'
const About = lazy(() => import('./About'))
const Home = lazy(() => import('./Home'))
const Home1 = lazy(() => import('./Home1'))

export default class LazyLoad extends Component {
  render() {
    return (
      <>
        <NavLink to="/about" tag="p">
          About
        </NavLink>
        <NavLink to="/home">Home</NavLink>

        <Suspense fallback={<Loading />}>
          <Switch>
            <Route path="/about" component={About} />
            <Route path="/home" component={Home} />
            <Route path="/home" component={Home1} />
          </Switch>
          <Redirect from="/" to="/about" />
        </Suspense>
        <style>
          {`
            .active {
              background-color: greenyellow;
            }
          `}
        </style>
      </>
    )
  }
}

 

Hooks

  Hook是react16.8.0新增的语法,可以在函数式组件中使用state及其他的react特性

  三个常用的hook

    1、useState

      const [count, setCount] = useState(0)

      第一次初始化指定的值会在内部做缓存

      setCount有两种写法:

        setCount(count + 1)
        //
        setCount(count => count + 1)

    2、useEffect

        useEffect(() => {
          const timer = setInterval(() => {
            setCount(count => count + 1)
          }, 1000)
          // 返回的函数相当于componentWillUnmount钩子,组件卸载前执行,在这里清除定时器、取消订阅等
          return () => {
            clearInterval(timer)
          }
        }, [])

      (1)第一个参数:回调函数,可以执行ajax请求,开启定时器等操作

      (2)第二个参数:

        第一种情况:不传参,执行1+n次,初始化调用一次,监听所有的state,有任何一个发生改变,都会再次执行一次(有点像render(1+n);也有人说像componentDidUpdate,但是这个钩子初始化不执行,状态发生变化时才会执行(n))    不传参的情况在实际开发中较少见,要么传个[],要么传个数组里面监听状态

        第二种情况:传 [],不监测任何状态,回调函数只会在第一次render后执行,相当于componentDidMount钩子,可以在这里进行ajax请求、开启定时器、订阅消息

        第三种情况:传 [count, name],初始化执行一次,并且监听count和name的状态,如果发生改变,执行回调函数(有点像vue中的computed)

      (3)返回值:返回一个函数,该函数相当于componentWillUnmount钩子,组件卸载前执行,在这里清除定时器、取消订阅等

    3、useRef

          const inputRef = useRef()
          return (
            <div>
              <input type="text" ref={inputRef} /><button onClick={() => console.log(inputRef.current.value)}>
                点击打印input框内容
              </button>
              <button onClick={() => (inputRef.current.value = '123')}>
                设置input框内容
              </button>
            </div>
          )

      Ref Hook可以在函数式组件中存储/查找组件内的标签或其他数据。功能和类式组件中的createRef()一样

 

Fragment标签

  <Fragment key={1}></Fragment>和<></> 都是不渲染出真实的根标签,减少一层dom结构,为了满足jsx的语法规范

  Fragment标签只能接收一个参数key,如果当前组件不参与遍历,使用空标签即可,如果参与遍历,可以使用Fragment标签,传入key值

 

context

  概念:

    用于【祖组件】和【后代组件】间通信,类似于vue中的provide / inject

    实际开发中一般不用context,使用react-redux中的Provider组件,在入口文件中将App组件包裹起来可以将state传到每个组件中

    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.querySelector('#root')
    )

  使用:

    1、通过Provider的value属性传递数据

    2、类组件可以通过static关键字声明接收context,也可以通过Consumer组件获取context

    3、函数式组件可以通过useContext钩子接收context,也可以通过Consumer组件去获取context

import React, { Component } from 'react'

const Context = React.createContext()
const { Provider, Consumer } = Context

export default class A extends Component {
  state = { name: '小明', age: 18 }
  render() {
    const { name, age } = this.state
    return (
      <div className="box a">
        <h2>A组件</h2>
        <p>用户名:{name}</p>
        {/* 1、通过Provider包裹子组件,子组件及子组件中的组件都具有context */}
        <Provider value={{ name, age }}>
          <B />
        </Provider>
        <style>
          {`
            .box{padding:8px;}
            .a {background-color: greenyellow;}
            .b {background-color: yellowgreen;}
            .c {background-color: deeppink;}
            .d {background-color: pink;}
          `}
        </style>
      </div>
    )
  }
}

class B extends Component {
  render() {
    return (
      <div className="box b">
        <h3>B组件</h3>
        <C />
      </div>
    )
  }
}

class C extends Component {
  static contextType = Context // 2、类式组件的context中如果需要有值,必须写上context声明
  render() {
    return (
      <div className="box c">
        <h4>C组件</h4>
        <p>类式组件接收用户名(第一种方式):{this.context.name}</p>
        <p>
          <Consumer>
            {({ name }) => `类式组件接收用户名(第二种方式):${name}`}
          </Consumer>
        </p>
        <D />
      </div>
    )
  }
}

function D() {
  return (
    <div className="d">
      <h5>D组件</h5>
      <p>
        {/* 3、函数式通过Consumer组件来获取context */}
        <Consumer>{({ name }) => `函数式组件接收用户名:${name}`}</Consumer>
      </p>
    </div>
  )
}

  效果:

    

 

组件优化(解决render无效调用)

  Component的2个问题:

    1、只要执行setState(),即使不改变数据,组件也会重新render,导致效率低

    2、只要当前组件重新render,就会触发子组件重新render,即使子组件中没有使用父组件的任何数据,这也会导致效率低

  期望的做法:

    只有当组件的state或props发生变化时才调用render

  原因:

    shouldComponentUpdate钩子默认返回true,这导致this.setState({})会调用render函数,但是这啥也没改,调用render就是浪费性能

  解决:

    第一种:

      // 重写shouldComponentUpdate钩子,比较新旧state或props,有变化返回true,没有返回false。这样写的好处:this.setState({})将不会触发render
      shouldComponentUpdate(nextProps, nextState) { // 该钩子是在render前调用的,但是可以拿到render后的props和state,所以参数名叫nextProps、nextState
        return this.state.count !== nextState.count
      }

      缺点:当state和props是多个时,需要写的判断太多了

    第二种:

      使用PureComponent替代Component,PureComponent重写了shouldComponentUpdate钩子,只有当state或props数据有变化才会触发render

      注意:不要直接对state进行修改,如下代码react不支持

          <button
            onClick={() => {
              // this.setState({ count: count + 1 })
              const state1 = this.state // 浅复制会导致
              // const state1 = JSON.parse(JSON.stringify(this.state))
              state1.count += 1
              this.setState(state1)
            }}
          >
            点击+1
          </button>

        为什么:这里要求回调函数是一个纯函数。如果对this.state进行深复制就可以这么用,新对象和state间就没有关系了

        特别注意纯函数中容易踩得坑:使用数组的push、unshift、splice等方法,这都是直接改变数组的,然后this.setState(arr),这不是改了原来的state了么

 

render props

  如何将组件标签中的内容展示出来?

    vue:vue插槽

    react:

      第一种:children props(标签中的内容是一个特殊的标签属性(props传参方式就是写标签属性),属性名叫children)  缺点:如果B组件需要A组件内的数据,做不到

import React, { PureComponent } from 'react'
import SetState from '../01setState'

export default class index extends PureComponent {
  render() {
    return (
      <>
        <h2>父组件</h2><A>
          <B name={'props小明'} />
        </A>
      </>
    )
  }
}

class A extends PureComponent {
  state = { name: '小明', age: 18 }
  render() {
    return (
      <>
        <h3>A组件</h3>
        {this.props.children}</>
    )
  }
}

class B extends PureComponent {
  render() {
    return (
      <>
        <h3>B组件</h3>
        <p>接收到props的name:{this.props.name}</p>
      </>
    )
  }
}

      第二种:render props(类似vue的slot)  A组件中通过标签属性render(可以自由命名,但一般都是命名为render),值是一个函数,返回B组件,函数的参数就是A组件中调用render时传的值。

import React, { PureComponent } from 'react'
import SetState from '../01setState'

export default class index extends PureComponent {
  render() {
    return (
      <>
        <h2>父组件</h2>
        <A render={values => <B name={values.name} />} /></>
    )
  }
}

class A extends PureComponent {
  state = { name: '小明', age: 18 }
  render() {
    return (
      <>
        <h3>A组件</h3>
        {this.props.render(this.state)}
      </>
    )
  }
}

class B extends PureComponent {
  render() {
    return (
      <>
        <h3>B组件</h3>
        <p>接收到A组件的name:{this.props.name}</p>
      </>
    )
  }
}

        效果:

          

 

错误边界

  组件中的子组件和后代组件,一旦发生错误会导致当前组件的渲染被阻塞,错误边界就是让拦截错误的扩散,哪个子组件出错了就展示备用文案或组件,从而不影响到自身的渲染。

  在当前组件中使用getDerivedStateFromError生命周期拦截错误,并且只能捕获子组件/后代组件生命周期(主要是应用于render)产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

import React, { Component } from 'react'
import Child from './Child'

export default class Parent extends Component {
  state = { hasError: '', name: '小明' }
  // 当子组件或后代组件发生错误时触发
  static getDerivedStateFromError(err) {
    console.log(err)
    return { hasError: err } // render之前触发,需要返回一个对象,对state中的hasError重新赋值
  }
  componentDidCatch(error, info) {
    console.log('统计错误,反馈给服务器')
  }
  render() {
    console.log(this)
    return (
      <>
        <h2>Parent组件</h2>
        {this.state.hasError ? 'Child组件中有错误' : <Child />}
        <h3>名字:{this.state.name}</h3>
      </>
    )
  }
}

 

组件间通信方式总结

  1、props:

    children props

    render props(相当于children props的升级,类似vue的slot)

  2、消息订阅-发布:

    pubsub-js、event等

  3、集中式管理:

    redux、dva等

  4、context:

    生产者-消费者模式

 

  搭配方式:

    父子组件:props

    兄弟组件:消息订阅-发布、集中式管理

    祖孙组件:消息订阅-发布、集中式管理、context(多用于插件封装)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

x

posted @ 2021-01-12 16:38  吴小明-  阅读(1351)  评论(0编辑  收藏  举报