React知识点笔记

151 阅读8分钟

此为个人学习react过程中的知识点记录

类组件

state中的数据应该是不可变的

this.state = {
	friends:['lilei', 'lily', 'lucy']
}
  • 在开发中不要这样做:

    this.state.friends.push('tom')
    this.setState({
    	friends: this.state.friends
    })
    

    原因:改变了原数组。

  • 在开发中推荐这样做:

    this.setState({
    	friends: [...this.state.friends, 'tom']
    })
    // 或者保存一个新的数组
    const newFriends = [...this.state.friends]
    newFriends.push('tom')
    this.setState({
    	friends: newFriends
    })
    

setState异步更新

setState的更新在一般情况下都是异步的,原因:

  • 如果每次调用 setState都进行一次更新,那么意味着render函数会被频繁调用,界面重新渲染,这样效率是很低的;
  • 最好的办法应该是获取到多个更新,之后进行批量更新;

如果同步更新了state,但是还没有执行render函数,那么state和props不能保持同步,会导致很多问题。

如何获取异步的结果

方式一:setState的回调 p setState接受两个参数:第二个参数是一个回调函数,这个回调函数会在更新后会执行; p 格式如下:setState(partialState, callback)

this.setState({
	message: "你好"
}, () => {
	console.log(this.state.message) // 你好
})

方式二:生命周期函数

componentDidUpdate(prevProps, prevState) {
	console.log(this.state.message) // 你好
}

一定是异步吗?

分为两种情况:

  • 在组件生命周期或React合成事件中,setState是异步;
  • 在setTimeout或者原生dom事件中,setState是同步;

数据的合并

多次对同一个变量进行更改,会合并为一次。

increment() {
	this.setState({
		counter: this.state.counter + 1
	})
	this.setState({
		counter: this.state.counter + 1
	})
	this.setState({
		counter: this.state.counter + 1
	})
}
// 合并后只会 + 1
// 如果有需求,可以使用函数。
increment() {
	this.setState((state, props) => {
		return {
			counter: state.counter + 1
		}
	})
	this.setState((state, props) => {
		return {
			counter: state.counter + 1
		}
	})
	this.setState((state, props) => {
		return {
			counter: state.counter + 1
		}
	})
}
// 函数中的参数:state会保留最新的state。
// 结果为 + 3

Component与PureComponent

  • 当类组件继承Component时,父组件重新render时,子组件也会重新执行render函数。这是不必要的。

  • 我们可以通过shouldComponentUpdate方法实现性能优化。

  • React给我们提供了一个生命周期方法 shouldComponentUpdate(很多时候,我们简称为SCU),这个方法接受参数,并且需要有返回值:

  • 返回值为true,则需要调用render函数,false则不需要。

  • 但当我们继承PureComponent时,则不需要再手动进行这样的操作。(仅限类组件)。

  • 函数组件如果需要进行这样的优化,可以使用memo函数进行包裹。

Ref

字符串

<h2 ref="titleRef">aaa</h2>
console.log(this.refs.titleRef)

对象

constructor(props) {
	super(props)
	this.titleRef = React.createRef()
}
<h2 ref={this.titleRef}>aaa</h2>
console.log(this.titleRef.current)

函数

constructor(props) {
	super(props)
	this.titleRef = null
}
<h2 ref={(arg) => {this,titleRef = arg}}>aaa</h2>
console.log(this.titleRef)
  • 类组件可以通过ref获取组件实例。
  • 函数组件不能。

ref的转发( React.forwardRef)

// React.forwardRef为高阶组件
// 定义一个函数组件,使用forwardRef进行包裹,返回一个组件
// forwardRef会对传入的组件做一个增强,增加ref参数
// 父组件就可以在子函数组件上增加ref属性,子函数组件就可以在这个ref参数中访问到。
const fun = forwardRef(function (props, ref) {
	return (
		<h2>aaa</h2>
	)
})

受控组件

  • 在 HTML 中,表单元素通常自己维护 state,并根据用户输入进行 更新。
  • 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
  • 我们将两者结合起来,使React的state成为“唯一数据源”;
  • 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作;
  • 被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”;

Context

  • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props;
  • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

Context的API

React.createContext

  • 创建一个需要共享的Context对象
  • 如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;
  • defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
const MyContext = React.createContext({
	aaa: aaa,
	bbb: bbb,
}) // defaultValue

Context.Provider

  • 每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化
  • Provider 接收一个 value 属性,传递给消费组件;
  • 一个 Provider 可以和多个消费组件有对应关系;
  • 多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
  • 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
// 把Parent父组件的state共享出去。
<MyContext.Provider value={this.state}> 
	// 把需要共享数据的组件作为Provider的子组件
	<Parent />
</MyContext.Provider>

Class.contextType

// 在类组件中导入MyContext
Child.contextType = myContext
// 就可以在this.context中访问到共享的数据了
// 如果Parent组件没有放入Provider中,会访问到defaultValue

Context.Consumer

// 如果是函数组件,导入MyContext
function Child() {
	// 消费者
	return (
		<div>
			<MyContext.Consumer>{
				value => {
					return (
						<div>
							<h2>{value.aaa}</h2>
							<h2>{value.bbb}</h2>
						</div>
					)
				}
			}
			</MyContext.Consumer>
		</div>
	)
}

Hooks

useState

使用方法

import React, { useState } from 'react';
import { connect } from 'dva';

export default function Other() {
  /**
   * Hook useState
   * 本身是一个函数,返回一个数组
   * 参数:给创建出来的状态一个默认值
   */

  // 设置当前state初始值为0
  // const arr = useState(0)
  // const state = arr[0]
  // 第二个参数可以用来改变默认值
  // const setState = arr[1]

  // 在真实开发中通常把状态和设置状态的函数从数组中结构出来:
  const [count, setCount] = useState(0)

  return (
    <div>
      <h2>当前计数:{count}</h2>
      <button onClick={e => {setCount(count + 1)}}>+1</button>
      <button onClick={e => {setCount(count - 1)}}>-1</button>
    </div>
  )
}

改变复杂数据类型内的元素

const [list, setList] = useState(['a', 'b'])

return (
	<div>
		list.map((item, index) => {
			return <li>{item}</li>
		})
		// 不能直接用push改变list中的元素,否则list的引用没有改变,不会触发页面刷新。
		<botton onClick={e => {setList([...list, 'c'])}}>添加元素</botton>
	</div>
)

useEffect(代替生命周期)

使用方法

  /**
   * useEffect
   * 参数1:回调函数,在mount和update阶段都会调用
   * 该参数有返回值:函数,在组件更新时会先调用参数中的回调函数,再调用返回的回调函数
   *
   * 参数2:数组,依赖项。只有依赖项改变的时候参数1执行
   */

  useEffect(() => {
    document.title = count;
    console.log('组件被订阅');
    return () => {
      console.log('取消了订阅');
    };
  }, [count]);

特点

  • 可定义多个useEffect,按照定义的顺序执行。

useLayoutEffect

  • 如果使用useEffect,那么当依赖发生改变时,会先重新渲染页面,再执行useEffect函数。
  • 而useLayoutEffect则相反,所以说他会阻塞渲染。

useContext

使用方法

/**
  * useContext
  * 参数:context对象,返回value
  */

const other = useContext(OtherContext);

useReducer(useState替代方案)

使用方法

  /**
   * useReducer
   * 作为useState替代方案
   * 返回值也是一个数组 0:状态,1:dispatch派发action
   * 参数1:reducer函数(纯函数)
   * 参数2:初始值
   */

  const reducer = (state, action) => {
    switch (action.type) {
      case 'increment':
        return { ...state, count: state.count + 1 };
      case 'decrement':
        return { ...state, count: state.count - 1 };
      default:
        return state;
    }
  };

  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <div>
      <h2>当前计数:{state.count}</h2>
      <button
        onClick={(e) => {
          dispatch({ type: 'increment' });
        }}
      >
        +1
      </button>
      <button
        onClick={(e) => {
          dispatch({ type: 'decrement' });
        }}
      >
        -1
      </button>
    </div>
  );

useCallback

使用方法

// 返回一个memoized函数。
// 参数1:函数
// 参数2:依赖项
// 只有在依赖项发生改变的时候才会调用
const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo

使用方法

// 返回一个memoized。可以是函数变量等
// 参数1:函数,返回值会赋值给memoized
// 参数2:依赖项
// 只有在依赖项发生改变的时候才会调用
const info = {a: 'aaa', b: 'bbb'}
const memoizedCallback = useMemo(
  () => {
    // return doSomething(a, b);
    return info
  },
  // [a, b]
  [info]
);

useCallback,useMemo的使用场景

  • 如果组件中的函数传入了子组件,那么当父组件发生更新时,子组件的入参发生更新,子组件就会重新渲染。
  • 将组件中的函数传递给子组件进行回调使用时,用useCallback进行处理。
  • 将组件中的数据传递给子组件使用时,用useMemo进行处理。
  • 子函数组件需要包裹memo。
  • useMemo是对其返回值做优化,useCallback是对于其第一个参数(函数)做优化

useRef

使用方法

// 在类组件中,我们使用React.createRef()获取dom或类组件实例
this.myRef = React.createRef()

// 在hooks开发中,可以直接使用useRef()
const titleRef = useRef()
titleRef.current.innerHTml = 'aaa'

<h2 ref={titleRef}></h2>

特点

  • useRef()返回一个ref对象,在组件的整个生命周期保持不变

    const [count, setCount] = useState(10)
    const numRef = useRef(count)
    // 组件刷新,重新渲染,值保持不变
    console.log(numRef.current) // 10 
    
    // 应用
    // 一个数字点击按钮+1,页面上分别显示+1后的值和上一次的值。
    // 运用useEffect
    
    useEffect(() => {
    	numRef.current = count
    }, [count])
    // 在触发useEffect的时候,页面已经渲染完毕了
    
  • 只有类组件有实例,函数组件没有。

useImperativeHandle

  • 与React.forwardRef一起使用,可以控制子组件向父组件暴露的内容。

    import React, { useRef, forwardRef, useImperativeHandle } from 'react';
    
    const HYInput = forwardRef((props, ref) => {
      const inputRef = useRef();
    // 只向父组件暴露该方法
    // () => ({}) 表示返回这个对象
      useImperativeHandle(ref, () => ({
        focus: () => {
          inputRef.current.focus();
        }
      }), [inputRef])
    
      return <input ref={inputRef} type="text"/>
    })
    
    export default function UseImperativeHandleHookDemo() {
      const inputRef = useRef();
    
      return (
        <div>
          <HYInput ref={inputRef}/>
          <button onClick={e => inputRef.current.focus()}>聚焦</button>
        </div>
      )
    }
    
    

注意事项

  • 只能在React函数组件最外层调用

  • 不要在循环条件判断其他js函数子函数中使用

自定义hook

  • react不允许在普通函数中调用hooks;
  • 我们可以给普通函数命名为usexxxx,就可以在里面调用hooks函数了;
  • 这个我们自己定义的usexxxx函数称为自定义hook。
  • 自定义的hook可以代替高阶组件。