加深react&hooks

147 阅读6分钟

react

jsx就是javascript XML的简写,表示在js代码中写XML(HTML)格式的代码 优势:

  1. 写JSX就跟写HTML一样,更加直观,友好
  2. JSX语法更能体现React的声明式特点(描述ui长什么样子)

事件绑定this指向

1.class的实例方法
getData = () => {}
2.箭头函数
<Button onClick={() => this.getData()} />
3.bind
this.getData =  this.getData.bind(this)

受控组建 this.state.value ----this.setState() 可以给多个input、select设置name 统一控制 非受控组建

this.texRef = React.createRef();
<input type='text' ref={this.texref} />
<button onChick={()=> console.log('文本框值为:',this.txtRef.current.value)}></button>

props 数据只读

  1. 父组件--子组件 props
  2. 子组件--父组件 回调函数
  3. 兄弟组件--状态提升 在最近的父组件中定义方法及初始值
  4. 跨组件传递

使用步骤

1.调用React.createContext()创建Provider(提供数据)和Consumer(消费数据)两个组件
//创建两个组件 Provider,Consumer
//let {Provider,Consumer}=React.createContext(defaultValue); //defaultValue可以设置共享的默认数据 当Provider不存在的时候 defaultValue生效
const { Provider, Consumer } = React.createContext({ theme: "green" })
function App() {
  return (
    <>
      <Provider value={{ theme: 'pink' }}>
        <Content />
      </Provider>
    </>
  )
}
//中间组件
function Content() {
  return (
    <div>
      <Button />
    </div>
  )
}
//接收组件,如果子组件是Consumer的话,将value作为参数值,传递给新创建的Consumer渲染一个函数组件
function Button() {
  return (
    <Consumer>
      {({ theme }) => (
        <button
          style={{ backgroundColor: theme }}>
          Toggle Theme
        </button>
      )}
    </Consumer>
  )
}

props.children

当组件中使用的标签中有文字时可以使用props.children获取
<App>我是子节点呀</App>
props.children ---> 我是子节点呀
children 就是一个特殊的props属性

组件的生命周期 挂载阶段

  1. constructor 组件创建时 //1.初始化state 2.为事件处理函数绑定this
  2. render 每次渲染都会触发
  3. componentDidMount 组件挂载(完成Dom渲染)后//1.发送网络请求 2.可以进行Dom操作

更新阶段

  1. 组件接收新props 2.setState 3.forceUpdate 执行条件
  2. render
  3. componentDidUpdate 注:函数更新时 必对比上一次的数据和当前的数据 if else 因为它拿的是更新后的数据

卸载

  1. componentWillUnmount //清理定时器

render props 模式使用步骤

方法1:
function App(props) {
  return (
    <Child
    getData={data => { 3.组件使用工具组件,获取暴露的方法,拿到数据,渲染Ui
      return(
        <p>{data.name}</p>
      )
    }}
    />
  )
}
class Child extends React.Component { 
1.方法组建提供复用的状态逻辑代码(1.状态 2.操作状态的方法
  state = {
    name: 'bob'
  }
  render() {
2.方法组件要复用的状态数据,通过this.props.getData方法的参数,暴露到组件的外部
    return this.props.getData(this.state)
  }
}
方法2: render porps方式渲染
function App(props) {
  return (
    <Child>
      {data =>{
        return (
          <div>{data.name}</div>
        )
      }}
    </Child>
  )
}
class Child extends React.Component {
  state = {
    name: 'bob'
  }
  render() {
    return this.props.children(this.state)
  }
}

高阶组件 目的:实现状态逻辑复用 displayName 设置多个高阶组件的名字

function WithMouse(WrappedComponent) {
  // class Mouse extends React.Component {
  //   state = {
  //     x: 0,
  //     y: 0
  //   }
  //   handMouseMove = e => {
  //     this.setState({
  //       x: e.clientX,
  //       y: e.clientY
  //     })
  //   }
  //   componentDidMount() {
  //     window.addEventListener('mousemove', this.handMouseMove)
  //   }
  //   componentWillUnmount() {
  //     window.removeEventListener('mousemove', this.handMouseMove)
  //   }
  //   render() {
  //     return <WrappedComponent {...this.state} {...this.props}/>
  //   }
  // }
  // return Mouse;
  const Mouse = () => {
    const [x, setX] = useState(0);
    const [y, setY] = useState(0);
    const handMouseMove = e => {
      setX(e.clientX)
      setY(e.clientY)
    }
    useEffect(() => {
      window.addEventListener('mousemove', handMouseMove)
      return () => {
        window.removeEventListener('mousemove', handMouseMove)
      }
    }, [])

    const state = {
      x, y
    }
    return <WrappedComponent {...state} />
  }
  return Mouse;
}

const Position = props => {
  return (
    <p>我的名字是:{props.name}鼠标当前的位置 x:{props.x} y:{props.y}</p>
  )
}

const MousePositon = WithMouse(Position);

function App() {
  return (
    <div>
      <h1>高阶组建</h1>
      <MousePositon name={'bob'}/>
    </div>
  )
}

this.setState 调用多次只触发一次render

state = 1;
Chick =()=>{
    this.setState({count:this.state.count + 1})
    this.setState({count:this.state.count + 1}) 
}//count为2
Chick = () => {
  this.setState((state, props) => { 回掉函数写法
    return {
      count: state.count + 1
    }
  })
  this.setState((state, props) => { //state可拿到最新值
    return {
      count: state.count + 1
    }
  })
}//count为3

//在状态更新后(页面完成重新渲染)后立即执行某个操作
this.setState(
 (state, props)=>{},
 () => {console.log('这个回调函数会在状态更新后立即执行,可以操作Dom')}
)

组件更新机制

this.setState作用
1.修改state 2.更新组件(Ui)
父组件更新,它下面的所有子组件都会向下流更新

组建性能优化

  1. state只存储和页面渲染相关的数据,计时器id这种可以直接放在this中
  2. 因为父组件更新会影响子组件所以为了避免不必要的更新
  • 组件使用nexProps 最新的nexProps.count 当前的 this.props.count
  • 自身使用nextState 最新的nextState.count 当前的 this.state.count
先走 shouldComponentUpdate(nexProps, nextState){
        条件判断
        true表示渲染
        false表示不重新渲染
    }
再根据return true或者false 走render

3.pureComponent 因为在pureComponent 中引用类型比较是看地址 ,所以想要改变一个引用类型的值,应该创建数据,不要直接修改数据,否则pureComponent会认为没有改变,无法更新

[]
不要使用push / unshift 等直接修改当前数组的方法
使用concat或slice等这些返回新数组的方法
this.setState({
   list:[...this.state.list,{新数据}] 
})
obj
this.setState({
   obj:[...this.state.obj,{新数据}] 
})

虚拟Dom和diff算法 1.虚拟Dom:本质上就是一个js对象,用来描述屏幕上看到的内容Ui(html结构

虚拟dom.png

执行过程

  1. 初次渲染时,React根据初始state,创建虚拟dom树
  2. 根据虚拟Dom生成真正的Dom,渲染到页面中
  3. 当数据改变(this.setState(),重新根据新的数据,创建新的虚拟dom树
  4. 与上一次的虚拟dom对象,使用Diff算法对比(找不同,得到需要更新的内容
  5. 最终,react只将变化的内容更新(patch)到DOM中,重新渲染页面

render时候会重新生成虚拟DOM对象 和初始的虚拟Dom对比 虚拟Dom的作用不仅是提升了react的性能,更多的是可以让js在更多平台运行

useEffect()

componentDidMount() + componentDidUpdate()

useCallback() + memo

import React, { useCallback, useState, memo } from 'react';
import Expensive from './components/Expensive';
import Child from './components/Child';

const MemoExpensive = memo(Expensive); //组件使用memo 方式1

const AddAssetsDetail = () => {
  const [data1, setData1] = useState(0);
  const [data2, setData2] = useState(0);

  const onClick1 = () => {
    setData1(num => num + 1);
  };

  const onClick2 = useCallback(() => {
    setData2(num => num + 1);
  }, []);

  return (
    <div>
      <Child onClick={onClick1} data={data1} />
      <MemoExpensive onClick={onClick2} data={data2} />
    </div>
  )
}

export default AddAssetsDetail;


import React, { memo } from 'react';
import { Button } from 'antd';

const Expensive = memo((props) => {  //组件使用 memo 方式2
  const { data, onClick } = props;
  console.log('Expensive执行了。。。。。');

  return (
    <div>
      Expensive.....{data}
      <Button onClick={onClick}>点击增加</Button>
    </div>
  )
})

export default Expensive;

import React from 'react';
import { Button } from 'antd';

function Child(props) {

  const { data, onClick } = props;
  console.log('Child执行了。。。。。');

  return (
    <div>
      Child.....{data}
      <Button onClick={onClick}>点击增加</Button>
    </div>
  )
}

export default Child;

memo是 React v16.6.0 新增的方法,与 PureComponent 类似,前者负责 Function Component 的优化,后者负责 Class Component。它们都会对传入组件的新旧数据进行浅比较,如果相同则不会触发渲染。

所以useCallback保证了onClick2不发生变化,此时点击Child组件不会触发Expensive组件的刷新,只有点击Expensive组件才会触发。在实现减少不必要渲染的优化过程中,useCallbackmemo是一对利器

useMomo()

import React, { memo, useMemo } from 'react';
import { Button } from 'antd';

const Child = memo((props) => {
  const { a, b } = props;

  const sum = () => {
    //a,b值改变触发了大量的运算
  };

  const result = useMemo(() => { sum() }, [a, b]);

  return (
    <div>
      <Button onClick={result}>点击增加</Button>
    </div>
  )
})

export default Child;

useContext()

父组件

import React, { useState } from "react";
import ReactDom from 'react-dom';
import Child from './Child';

export const NumberContext = React.createContext(); (第1const App = () => {
  const [number, setNumber] = useState(2022);
  return (
    <NumberContext.Provider value={number}> (第2步
      <Child />
    </NumberContext.Provider>
  )
}

ReactDom.render(<App />, document.getElementById('app'));

中间组件

import React from "react";
import SmallChild from './SmallChild';

const Child = () => {
  return (
   <div>
      <SmallChild></SmallChild>
      <div>这个是中间组建自己的逻辑</div>
   </div>
  )
}

export default Child;

孙子组件

import React, { useContext } from "react";
import { NumberContext }  from './index'; (第3const SmallChild = () => {
  let number = useContext(NumberContext); (第4return (
    <h2>这个是父组建传过来需要展示的数据,今年是{number}年</h2>
  )
}

export default SmallChild;

useReducer()

import React, { useReducer } from "react";
import ReactDom from 'react-dom';
import '$css/index.less';

const initialState = 999
const reducer = (state, action) => {
  switch (action) {
    case 'add':
      return state + 2
    case 'sub':
      return state - 2
    case 'reset':
      return initialState
    default:
      return state
  }
}
const App = () => {
  const [number, dispatch] = useReducer(reducer, initialState);//默认值
  return (
    <div>
      <h2>点击{number}就进行加减</h2>
      <button onClick={() => { dispatch('add') }}>add点我吧!</button>
      <button onClick={() => { dispatch('sub') }}>sub点我吧!</button>
    </div>
  )
}

ReactDom.render(<App />, document.getElementById('app'));