useRef 获取dom元素 & 注册抛出组件方法数据 & 静态的跨渲染时机的状态
const initialState = 0;
const iref = useRef(initialState);
返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialState )。
只会在初始化的钩子生成一次
当current的值发生改变时并不会触发render
可以跨渲染时机拿到值
获取操作dom元素
import React, { useRef } from "react";
const RefDomFiber = () => {
const inputRef = useRef<HTMLInputElement>(null);
return (
<div>
<input type="text" ref={inputRef} />
<button onClick={() => {
inputRef.current?.focus();
}}>input聚焦</button>
</div>
);
};
export default RefDomFiber;
注册一个ref,给input绑定上去,然后进行操作
可以跨渲染时机拿到值
当定义了一个useState的状态,给定一个延迟器然后console打印,在这期间进行数据state修改,在console打印时拿到的值依旧是延迟器之前的值。
const [num, setNum] = useState(0)
function clickhandle() {
console.log('触发了打印事件')
setTimeout(() => {
console.log(`num的值: ${num}`);
}, 3000);
}
return (
<>
<button onClick={() => {
setNum(num + 1);
console.log('num当前的值: ', num + 1)
}}>num: {num}</button>
<button onClick={clickhandle}>打印</button>
</>
);
原因: react每次render时,都会重新注册一遍函数,当点击打印的时候方法内部记录了当前这次render对应num的值,延迟期间修改num的值,之前的函数是不会拿到新的num
const iref = useRef(0);
const [num, setNum] = useState(iref.current)
function clickhandle() {
console.log('触发了打印事件')
setTimeout(() => {
console.log(`iref的值: ${iref.current}`);
}, 3000);
}
return (
<>
<button onClick={() => {
iref.current += 1
setNum(iref.current)
console.log('iref当前的值:', iref.current)
}}>iref的值:{iref.current}</button>
<button onClick={clickhandle}>打印</button>
</>
);
这里注册了一个state:num,还注册了一个ref:iref,因为ref的修改是不会触发render,所以在修改的时候把state也修改了让render触发,useRef相当于全局定义了一个数据,不同时机render注册的函数记录的都是同一个位置的ref
注册调用组件方法
1. 用forwardRef方法注册一个子组件,将父类的ref作为参数传入函数式组件中
2. 用useImperativeHandle给ref 抛出对应的属性方法
import React, { useRef, useState, forwardRef, useImperativeHandle } from "react";
const RefFiber = () => {
const iref = useRef<{
getParams?: InitialStateProps;
setParams?: (_props: InitialStateProps) => void;
}>();
const getChildValue = () => {
console.log('子组件数据:', iref?.current?.getParams)
}
const updataChildValue = () => {
iref?.current?.setParams?.({
id: 2,
name: '父组件修改',
title: '父组件修改'
})
}
return (
<>
<ChildFiber ref={iref} />
<button onClick={getChildValue}>获取子组件数据</button>
<button onClick={updataChildValue}>修改子组件数据</button>
</>
);
};
export default RefFiber;
interface InitialStateProps {
id: number;
name: string;
title: string;
}
const initialState = {
id: 1,
name: '子组件',
title: 'childFiber',
}
const ChildFiber = forwardRef((props, ref) => {
const [state, setState] = useState(initialState);
useImperativeHandle(ref, () => ({
getParams: state,
setParams: (_props: InitialStateProps) => {
setState(_props);
}
}))
return (
<div>
<div>id: { state.id }</div>
<div>name: { state.name }</div>
<div>title: { state.title }</div>
</div>
)
})
useContext 状态共享器
在开发中通常会遇到一个组件的state状态在多个子组件中传递的共用,如果一级一级的传递非常麻烦,useContext就是解决这个问题这个问题的
1. 首先要要在组件外部创建一个Context
import React, { useState, useContext, createContext } from "react";
interface InitialStateType {
id: number,
name: string,
}
const initialState = {
id: 1,
name: '1',
}
interface ContextType extends InitialStateType { setState: (value: InitialStateType) => void }
const AppContext = createContext<Partial<ContextType>>({})
先用createContext创建一个context,为什么要在组件外部创建,因为组件内部state修改都会重新render的把函数内部的东西重新注册一次,如果在组件内部创建就会导致每次render都生成一个新的context。
这里我给createContext传的类型是我用例上需要的,不要被误导
2. 在父组件使用Context的Provider包裹住需要共享状态的组件
const ContextFiber = () => {
const [state, setState] = useState(initialState);
return (
<div>
<AppContext.Provider value={{ ...state, setState }}>
<Child1 />
</AppContext.Provider>
</div>
)
};
使用实例的Context.Provider包裹住需要共享状态的组件,用value属性传递需要共享的状态及方法
3. 在子组件中用useContext生成对应的context
const Child1 = () => {
const contextValue = useContext(AppContext);
return (
<div>
Child1 <br />
<div>id: { contextValue?.id }</div>
<div>name: { contextValue?.name }</div>
<button onClick={() => {
contextValue?.setState?.({
id: 2,
name: '2',
})
}}>修改context</button>
</div>
)
}
使用useContext传入需要关联的context,返回的数据就会包裹父组件value里面的状态及方法
整体代码
import React, { useState, useContext, createContext } from "react";
interface InitialStateType {
id: number,
name: string,
}
const initialState = {
id: 1,
name: '1',
}
interface ContextType extends InitialStateType { setState: (value: InitialStateType) => void }
const AppContext = createContext<Partial<ContextType>>({})
const ContextFiber = () => {
const [state, setState] = useState(initialState);
return (
<div>
<AppContext.Provider value={{ ...state, setState }}>
<Child1 />
</AppContext.Provider>
</div>
)
};
export default ContextFiber;
const Child1 = () => {
const contextValue = useContext(AppContext);
return (
<div>
Child1 <br />
<div>id: { contextValue?.id }</div>
<div>name: { contextValue?.name }</div>
<button onClick={() => {
contextValue?.setState?.({
id: 2,
name: '2',
})
}}>修改context</button>
</div>
)
}