React中的hooks
学习这些内容需要一些前置知识,typescript的类型定义和react的hooks知识。 在看hooks的类型声明时建议先看_。
- useState
- useMemo
- useCallback
- useRef
- useReducer
- useContext
useState
useState提供函数组件也可以动态修改,相当于类组件的state; 给useState设置类型的时候通过范型传递进去:state的类型就被定义了,并且给了一个初始值
interface IProps {
name: string;
}
// React.FunctionComponent -> React.FC 支持缩写
const State: React.FunctionComponent<IProps> = (props: IProps) => {
let [count, setCount] = useState<number>(0);
return (<>
<div>
<h1>{count}</h1>
<h2>{props.name}</h2>
</div>
</>)
}
如果没有初始值那么可以使用联合属性将初始值设置为null,例如<number|null>,但是在这里需要注意,后续使用state的时候需要进行空值判断,通常使用可选链来进行访问
a?.b -> a && a.b
interface IObject {
num: number;
}
...
let [count, setCount] = useState<IObject | null>(null);
...
<h1>{count?.num}</h1>
useMemo
useMemo是渲染组件时的优化项,传递范型规定返回值(不过一般在使用中都不会去定义)
const memoized = useMemo<string>(() => {
return name;
}, [name])
useCallback
useRef
useRef相当于创建一个ref,有两个用途:
- 可以用于保存一个state的最新值,在异步处理state的时候也可以获取到最新的state值;
- 与标签关联起来,可以通过.current来获取到DOM元素。
保存state 属于一个MutableRefObject类型
const Ref: React.FC = () => {
let [count, setCount] = useState<number>(0);
let divRef = useRef<number>(0);
return (
<div>
<h1>{count}</h1>
<button onClick={() => {
setCount(count + 10);
divRef.current = count + 10;
}}>+10</button>
<button onClick={() => {
setTimeout(() => {
// 这里如果不做处理,那么延迟处理会拿不到最新的值,可以借助useRef的current属性来暂存值,这个一定拿到的最新值
setCount(divRef.current - 10);
divRef.current -= 10;
}, 1000)
}}>-10</button>
</div>
)
}
关联DOM,对于ref的类型选择在不知道的时候可以使用ts的类型判断,然后再确定。
const Ref: React.FC = () => {
let divRef = useRef<HTMLHeadingElement>(null);
return (
<div>
<h1 ref={divRef}>ref</h1>
<button onClick={(event: React.MouseEvent)=>{
console.log('button', event.target);
console.log('h1', divRef.current);
}}>获取DOM</button>
</div>
)
}
useReducer
在state比较复杂使用多个useState的时候可以采用useReducer来进行集中管控。
enum ActionType {
INCREMENT_COUNT = 'INCREMENT_COUNT',
DECREMENT_COUNT = 'DECREMENT_COUNT'
}
interface IState {
count: number;
}
interface IAction {
type: ActionType,
value: number;
}
const initializeState = {
count: 0
}
const reducer: React.Reducer<IState, IAction> = (state: IState, action: IAction) => {
switch (action.type) {
case 'INCREMENT_COUNT':
return {
...state,
count: state.count + action.value
}
case 'DECREMENT_COUNT':
return {
...state,
count: state.count - action.value
}
default:
return {
...state
}
}
}
const Reducer: React.FC = () => {
let [state, dispatch] = useReducer<React.Reducer<IState, IAction>>(reducer, initializeState);
return (
<div className="reducer">
<h1>{state.count}</h1>
<button onClick={() => {
dispatch({
type: ActionType.INCREMENT_COUNT,
value: 10
})
}}>+10</button>
<button onClick={() => {
dispatch({
type: ActionType.DECREMENT_COUNT,
value: 5
})
}}>-5</button>
</div>
)
}
useContext
通过useContext可以将数据通过上下文的形式进行传递
interface IArticle {
id: number;
title: string;
}
interface IContext {
articles: IArticle[];
style?: React.CSSProperties;
}
type IProps = {
name: string;
style: React.CSSProperties,
// 这里children可以写也可以省略,因为React.FunctionComponent已经内置了一个
// children: React.ReactNode[];
// children: React.ReactChild[];
// children: JSX.Element[];
}
const initlizeArticles = [
{
id: 1,
title: 'first'
},
{
id: 2,
title: 'second'
}
]
const ArticleContext = createContext<IContext>({} as IContext);
const ArticleProvider: FC<IProps> = ({ children, style }) => {
let [articles] = useState<IArticle[]>(initlizeArticles);
let context = { articles, style };
return (
<ArticleContext.Provider value={context}>
{children}
</ArticleContext.Provider>
)
}
const ShowArticles: FC = () => {
let { articles = [], style } = useContext<IContext>(ArticleContext);
return (
<div style={style}>
{
articles.map((article: IArticle) => <p key={article.id}>{article.title}</p>)
}
<input type="text" onChange={(e: React.ChangeEvent) => {
console.log('a', e);
}} value={'a'} />
</div>
)
}
const Article: FC = () => {
return (
<ArticleProvider name={'11'} style={{ color: 'red' }}>
<h1>My Title</h1>
<ShowArticles />
</ArticleProvider>
)
}
对于代码中提到的React.FunctionComponent内置了children属性,可以通过声明文件寻找到踪迹
// 在这里的props已经有了一个PropsWithChildren类型,内置了children
interface FunctionComponent<P = {}> {
(props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null;
}
type PropsWithChildren<P> = P & { children?: ReactNode | undefined };
hooks中props中的类型声明
- 一般的组件参数:
type IProps = {
message: string;
count: number;
disabled: boolean;
/** string类型的数组 两种方式 */
// names: string[];
names: Array<string>;
/** 字面量方式的精确控制,并且可以采用联合类型,必须为两者中的一个 */
status: "waiting" | "success";
/** 占位用,不能知道里面的属性,在不操作对象属性时可以使用,因为访问会报错,提示没有这个属性 */
obj: object;
/** 和object差不多的功能,但是这个属于精确捕捉,必须为 {} */
obj2: {};
/** 常用的对象声明,包含对象拥有的属性和对应属性值的类型 */
obj3: {
id: string;
title: string;
};
/** 对象类型的数组 */
objArr: {
id: string;
title: string;
}[];
objAry: Array<{
id: string;
title: string;
}>
/** 一个可扩展的对象,属性和属性值需要符合类型要求 */
dict1: {
[key: string]: MyTypeHere;
};
dict2: Record<string, MyTypeHere>; // 和dict1一样
/** 不做任何操作时可以这样定义函数 */
onSomething: Function;
/** 没有参数和返回值的函数 */
onClick: () => void;
/** 有参数id为number且没有返回值的函数 */
onChange: (id: number) => void;
/** button按钮传递一个鼠标点击事件,没有返回值 */
onClick(event: React.MouseEvent<HTMLButtonElement>): void;
/** 可选参数 */
optional?: OptionalType;
};
- 一些特殊的组件参数
总结:使用React.ReactNode来定义children
export interface AppProps {
children1: JSX.Element; // 接收一个jsx结构,只能有一个child
children2: JSX.Element | JSX.Element[]; // 可以接收多个child
children3: React.ReactChildren; // 尽管有这个属性,但是不实用
children4: React.ReactChild[]; // React.ReactChild = React.ReactElement|React.ReactText -> React.ReactText=string|number
children: React.ReactNode; // 在声明children中最好使用这个,包含了ReactChild | ReactFragment | ReactPortal | boolean | null | undefined
functionChildren: (name: string) => React.ReactNode; // 接收一个函数返回ReactNode
style?: React.CSSProperties; // 接收style属性
onChange?: React.FormEventHandler<HTMLInputElement>; // onChange事件的触发
// 更多参考: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
// TODO 暂时没明白如何完美的使用
props: Props & React.ComponentPropsWithoutRef<"button">; // to impersonate all the props of a button element and explicitly not forwarding its ref
props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // to impersonate all the props of MyButtonForwardedRef and explicitly forwarding its ref
}
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;