此为个人学习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可以代替高阶组件。