Open WangShuXian6 opened 4 years ago
获取 hook 所在元素节点
useDom
import React, {
Component,
useEffect,
useLayoutEffect,
useReducer,
useState,
useContext,
useRef,
useCallback,
useMemo
} from "react";
function useDom(): [{ ref: any }] {
const ref = useRef();
const bind = useMemo(() => {
return {
ref
};
}, []);
useEffect(() => {
console.log("ref dom:", ref.current);
}, [ref]);
return [bind];
}
export default useDom;
import useDom from '../useDom'
const DomExample = () => {
const [bindText] = useDom()
return (
<Text className='tip-bar' {...bindText}>现在开始</Text>
);
};
touch 事件
useTouch
import React from 'react';
function useTouch(): [
boolean,
{
onTouchStart: (e: React.TouchEvent) => void;
onTouchEnd: (e: React.TouchEvent) => void;
}
] {
const [isTouched, setTouched] = React.useState(false);
const bind = React.useMemo(
() => ({
onTouchStart: (e: React.TouchEvent) => void setTouched(true),
onTouchEnd: (e: React.TouchEvent) => void setTouched(false),
}),
[]
);
return [isTouched, bind];
}
export default useTouch;
useTouch 使用示例
const Example = () => {
const [isTouched, bind] = useTouch();
return (
<div {...bind}>
{isTouched ? '触摸到' : '未触摸到'}
</div>
);
};
onSubmit
–处理表单提交。
onChange
–处理更改任何表单输入值。每个表单都有这些事件处理程序
因此,可以重用
onSubmit
和onChange
useForm.js
自定义React Hooks使用特殊的命名约定,即放置“ use”在前 函数名称的名称,以便React知道该文件是一个Hook
useForm.ts
import { useState } from 'react';
const useForm = (initialValues: object, callback: Function) => {
// 该函数带有2个参数initialValues,callback。
// initialValues 是表单可能拥有的输入值组成的 name:value 对象
// callback 回调是从组件传递到自定义挂钩的函数。表单提交时都会调用它。
// 设置一个状态变量和一个setter函数,分别称为values和setValues,跟踪表单值
const [values, setValues] = useState<object>(initialValues);
// 该函数接受一个事件。它可以防止该事件的默认操作(在事件被调用后刷新页面)。之后,它仅调用callback()
const handleSubmit = (event) => {
if (event) event.preventDefault();
callback();
};
// 该函数也接收一个事件
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
// 返回handleChange,handleSubmit和值,以便我们的组件可以访问它们
return {
handleChange,
handleSubmit,
values,
}
};
export default useForm;
Form.jsx
import React from 'react';
import useForm from "./useForm";
const Form = () => {
//分解从useForm自定义React Hook返回的对象,以便我们可以使用值,handleChange和handleSubmit
const { values, handleChange, handleSubmit } = useForm({ email: '', password: '' }, login); // 初始化
// 向Form组件添加一个登录函数,并将其作为回调参数传递到useForm自定义Hook中
function login() {
console.log(values);
}
return (
// 将onSubmit属性添加到表单HTML元素,然后调用handleSubmit:
<form onSubmit={handleSubmit}>
<div className="field">
<label className="label">Email Address</label>
<div className="control">
{/* {电子邮件输入,向其添加onChange和value属性} */}
<input className="input" type="email" name="email" onChange={handleChange} value={values.email} required />
</div>
</div>
<div className="field">
<label className="label">Password</label>
<div className="control">
{/* {密码输入,向其添加onChange和value属性} */}
<input className="input" type="password" name="password" onChange={handleChange} value={values.password} required />
</div>
</div>
<button type="submit" className="button is-block is-info is-fullwidth">Login</button>
</form>
);
};
export default Form;
定义表单的验证规则
将任何错误存储在状态变量中
如果存在任何错误,阻止表单提交
LoginFormValidationRules.ts
export default function validate(values:object) {
// 该函数接受一个参数 name:value 格式的对象
//初始化一个称为errors的新对象
let errors = {};
// 为电子邮件输入字段添加一个验证规则
if (!values.email) {
errors.email = 'Email address is required';
} else if (!/\S+@\S+\.\S+/.test(values.email)) {
errors.email = 'Email address is invalid';
}
if (!values.password) {
errors.password = 'Password is required';
} else if (values.password.length < 8) {
errors.password = 'Password must be 8 or more characters';
}
// 返回错误对象,以便我们枚举useForm自定义挂钩中的错误
return errors;
};
useForm.ts
import { useState, useEffect } from 'react';
const useForm = (initialValues: object, callback: () => void, validate: (object) => object) => {
// 该函数带有3个参数initialValues,callback。
// initialValues 是表单可能拥有的输入值组成的 name:value 对象
// callback 回调是从组件传递到自定义挂钩的函数。表单提交时都会调用它。
// validate 验证函数 返回错误对象
// 设置一个状态变量和一个setter函数,分别称为values和setValues,跟踪表单值
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
// 防止表单在渲染时提交
const [isSubmitting, setIsSubmitting] = useState(false);
// useEffect替换了React Class组件中的componentDidMount和componentDidUpdate生命周期方法
// 侦听对错误的任何更改,检查对象的长度,如果errors对象为空,则调用回调函数
// [errors]更改的结果(副作用),请执行此操作
// 组件渲染时被调用,如果不检测 isSubmitting ,将在页面初始化的时候提交表单 执行 callback()
useEffect(() => {
if (Object.keys(errors).length === 0 && isSubmitting) {
callback();
}
}, [errors]);
// 该函数接受一个事件。它可以防止该事件的默认操作(在事件被调用后刷新页面)。之后,它仅调用callback()
const handleSubmit = (event) => {
if (event) event.preventDefault();
// 当用户提交表单时,我们首先要在提交表单之前检查其所有数据是否没有问题
setErrors(validate(values));
setIsSubmitting(true);
};
const handleChange = (event) => {
event.persist();
setValues(values => ({ ...values, [event.target.name]: event.target.value }));
};
// 返回handleChange,handleSubmit和值,错误对象,以便我们的组件可以访问它们
return {
handleChange,
handleSubmit,
values,
errors,
}
};
export default useForm;
Form.jsx
import React from 'react';
import useForm from "./useForm";
import validate from './LoginFormValidationRules';
const Form = () => {
//分解从useForm自定义React Hook返回的对象,以便我们可以使用值,handleChange和handleSubmit
const {
values,
errors,
handleChange,
handleSubmit,
} = useForm(login, validate);
// 向Form组件添加一个登录函数,并将其作为回调参数传递到useForm自定义Hook中
function login() {
console.log('No errors, submit callback called!');
}
return (
// 将onSubmit属性添加到表单HTML元素,然后调用handleSubmit:
<form onSubmit={handleSubmit} noValidate>
<div className="field">
<label className="label">Email Address</label>
<div className="control">
{/* {电子邮件输入,向其添加onChange和value属性} */}
{/* {通过检查errors对象是否具有与输入名称匹配的键来使用该类,如果是,则将 is-danger 类添加到输入的className中} */}
<input autoComplete="off" className={`input ${errors.email && 'is-danger'}`} type="email" name="email" onChange={handleChange} value={values.email || ''} required />
{/* 通过在输入元素下方添加内联条件来显示实际错误消息,以再次检查errors对象是否具有与该输入匹配的键,如果是,则以红色显示错误消息: */}
{errors.email && (
<p className="help is-danger">{errors.email}</p>
)}
</div>
</div>
<div className="field">
<label className="label">Password</label>
<div className="control">
{/* {密码输入,向其添加onChange和value属性} */}
<input className={`input ${errors.password && 'is-danger'}`} type="password" name="password" onChange={handleChange} value={values.password || ''} required />
</div>
{errors.password && (
<p className="help is-danger">{errors.password}</p>
)}
</div>
<button type="submit" className="button is-block is-info is-fullwidth">Login</button>
</form>
);
};
export default Form;
TaroMask.tsx
import React, { useEffect, useState } from 'react'
import { View } from "@tarojs/components";
import "./TaroMask.less";
// dts
// enum Direction {
// 'center' = 'center',
// 'top' = 'top',
// 'bottom' = 'bottom'
// }
// util
// type Definition
type OwnProps = {
isDisableMaskEvent?: boolean; //是否禁止背景点击关闭
direction?: 'center' | 'top';
visible: boolean;
onToggle?: () => void;
children: any;
};
export default function TaroMask({ isDisableMaskEvent, direction, visible, onToggle, children }: OwnProps) {
const [directionClassName, setDirectionClassName] = useState<string>('')
const [visibleClassName, setVisibleClassName] = useState<string>('taro-mask_container')
useEffect(() => {
const className = visible ? "taro-mask_container show" : "taro-mask_container"
setVisibleClassName(className)
}, [visible])
useEffect(() => {
console.log('direction:', direction)
if (direction === 'top') {
setDirectionClassName('top')
return
}
// if (direction === 'bottom') {
// setDirectionClassName('bottom')
// return
// }
setDirectionClassName('center')
}, [direction])
const toggle = () => {
if (isDisableMaskEvent) return
if (!onToggle) return
onToggle()
}
const stopEvent = (e) => {
e.preventDefault()
e.stopImmediatePropagation()
e.stopPropagation()
}
return (
<View className={`${visibleClassName} ${directionClassName}`} onClick={toggle}>
<View onClick={stopEvent}>
{children}
</View>
</View>
);
}
TaroMask.less
.taro-mask_container {
z-index: -1;
opacity: 0;
visibility: hidden;
position: fixed;
top: 0;
left: 0;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
overflow-x: hidden;
overflow-y: auto;
transition: all ease-in-out 0.2s;
background-color: rgba(0, 0, 0, 0.2);
}
.taro-mask_container.top {
position: fixed;
top: 0;
left: 0;
align-items: flex-start;
}
// .taro-mask_container.bottom {
// position: fixed;
// top: 0;
// left: 0;
// align-items: flex-end;
// }
.taro-mask_container.show {
position: fixed;
top: 0;
left: 0;
z-index: 100;
opacity: 1;
visibility: visible;
transition: all ease-in-out 0.2s;
}
useTaroMask.ts
import React, { useState, useRef } from "react";
import TaroMask from "./TaroMask";
export default TaroMask;
// dts
// enum Direction {
// "center" = "center",
// "top" = "top",
// "bottom" = "bottom"
// }
interface Params {
isDisableMaskEvent?: boolean; //是否禁止背景点击关闭
direction?: "center" | "top";
visible: boolean;
}
export const useTaroMask = (
params: Params
): [
boolean,
() => void,
{
onToggle: () => void;
visible: boolean;
direction: "center" | "top";
isDisableMaskEvent?: boolean;
}
] => {
const [visible, setVisible] = useState<boolean>(params.visible);
const countRef = useRef(params.visible);
countRef.current = visible;
const toggle = () => {
//if (params.isDisableMaskEvent && visible) return;
setVisible(!countRef.current);
};
return [
visible,
toggle,
{
onToggle: toggle,
visible,
direction: params.direction || "center",
isDisableMaskEvent: params.isDisableMaskEvent
}
];
};
example.tsx
//hooks
import TaroMask, { useTaroMask } from '../../hooks/taroMask/useTaroMask'
export default function Index({ }: OwnProps) {
const [tipVisible, toggleTip, tipBind] = useTaroMask({ visible: true }) // 首屏弹窗
useEffect(() => {
setTimeout(() => {
toggleTip()
}, 10000)
}, [1])
return (
<TaroMask {...tipBind}>
{/* 自定义内容区域 */}
</TaroMask>
)
}
别纠结它渲染多少次了,因为你的组件只是在拆分 UI,所以 UI 一致就行,逻辑一不一致不在你考虑范围内
视图 该如何处理?在下面的服务中,视图被视为 VM 适配器 如果你需要 props -> 处理 -> VM 的模型,那么你实际上干了分层的事情
import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react'; import logo from './logo.svg'; import './App.css';
function useServiceB(count: number) { const [value, setValue] = useState(count); useEffect(() => { setValue(count); }, [count]);
const B = useCallback(() => { console.log("子组件render"); return (
我是子组件
子组件的number是{value}
);
}, [value]); return B; }
function useServiceA() { const [count, setCount] = useState(0); const B = useServiceB(count); // 欢乐消依赖 const countRef = useRef(count); useEffect(() => { countRef.current = count; }, [count]);
const A = useCallback( () => (
我是父组件
父组件的count是{countRef.current}
),
[B]
); return A; }
function App() { const A = useServiceA(); return useMemo(() => , [A]); }
export default App;
分页查询 自定义响应标识【应使用HTTP标识】
import { useEffect, useState } from 'react';
import { queryNewsList } from '@/services/management/news';
interface Params { lazy?: boolean; page?: number; pageSize?: number; }
interface Return { loading: boolean; newsList: API.News[]; total: number; error: any; refetch: () => void; }
const useNewsList = ({ lazy = false, page = 1, pageSize = 10 }: Params): Return => {
const [loading, setLoading] = useState
const query = async () => { setLoading(true); try { const res = await queryNewsList({ page, pageSize }); const { status, reason, result } = res || {}; const { message } = reason || {}; const { list: newsList, total } = result || {}; const isSuccess = status === 0;
if (!isSuccess || !newsList) throw new Error(message);
setNewsList(newsList);
setTotal(total);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
};
useEffect(() => { if (lazy) return; query(); }, []);
return Object.assign([loading, newsList, total, error, query], { loading, newsList, total, error, refetch: query, }); };
export default useNewsList;
js
export const removeEmpty = (obj) => {
Object.keys(obj).forEach(
(key) =>
(obj[key] == null || obj[key] == undefined || obj[key] == "") &&
delete obj[key]
);
return obj;
};
import { removeEmpty } from "./common";
import { trackPromise} from 'react-promise-tracker';
const request = (url, { method, payload, headers }) => {
return new Promise((resolve, reject) => {
const timeHeader = {
"Content-Type": "application/json",
};
const headersWithTime = removeEmpty(
Object.assign({}, headers || {}, timeHeader)
);
const _payload = removeEmpty(payload);
trackPromise(fetch(url, {
method: method || "GET",
body: method === "GET" ? null : JSON.stringify(_payload || {}),
headers: headersWithTime,
})
.then((res) => {
res.text().then((responseText) => {
const response = JSON.parse(responseText);
return resolve(response);
});
})
.catch((error) => {
console.warn("error", error);
return reject(error);
}))
});
};
export default request;
import { useState, useEffect } from "react";
import request from "../utils/request";
const defaultFn = () => {};
const useRequest = (api, params = {}, callBack = {}, lazy) => {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
const _request = async (api, _params = {}, _callBack = {}) => {
const method = _params.method || params.method || "GET";
const query = _params.query || params.query || {};
const payload = _params.payload || params.payload || {};
const headers = _params.headers || params.headers || {};
const onCompleted =
_callBack.onCompleted || callBack.onCompleted || defaultFn;
const onSuccess = _callBack.onSuccess || callBack.onSuccess || defaultFn;
const onFail = _callBack.onFail || callBack.onFail || defaultFn;
setLoading(false);
const loadingIntervel = setInterval(() => {
setLoading(true);
}, 200);
try {
const params = {
method,
payload,
headers,
};
const res = await request(api, params);
const { status, reason, result = {} } = res || {};
const { message } = reason || {};
const isSuccess = status === 1;
if (!isSuccess) throw new Error(message);
setData(result);
onCompleted && onCompleted(result);
onSuccess && onSuccess(result);
return {
data: result,
success: true,
};
} catch (error) {
setError(error);
setData(null);
onFail && onFail(error);
} finally {
clearInterval(loadingIntervel);
setLoading(false);
onCompleted && onCompleted(null);
}
};
const refetch = (_params, _callBack) => {
_request(api, _params, _callBack);
};
useEffect(() => {
if (lazy) return;
refetch(
{
query: params.query || {}, //暂不支持
payload: params.payload || {},
},
{
onCompleted: callBack.onCompleted || defaultFn,
onSuccess: callBack.onSuccess || defaultFn,
onFail: callBack.onFail || defaultFn,
}
);
}, []);
return Object.assign([data, loading, error, refetch], {
data,
loading,
error,
refetch,
});
};
export default useRequest;
useJob
import useRequest from "../hooks/useRequest"; import { jobApi } from "../config/api";
const useJob = ({ empId, lazy }={}) => { const { data, loading, error, refetch } = useRequest( jobApi, { method: "POST", payload: { empId }, }, {}, lazy );
const _refetch = (params) => { refetch(); };
return Object.assign([data, loading, error, _refetch], { job: data, loading, error, refetch: _refetch, }); };
export default useJob;
>
import { useJob } from "../../hooks";
const { loading, job } = useJob({ empId: id });
src\useQuery.ts
import { useState, useEffect } from "react";
export interface QueryResult<T,P> {
data: T | null;
error: Error | null;
isLoading: boolean;
exec: (p: P) => void;
}
// 通用请求 hooks
const useQuery = <T, P>(
queryFn: (params: P) => Promise<T>,
params: P
): QueryResult<T,P> => {
const [data, setData] = useState<T | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isLoading, setIsLoading] = useState<boolean>(false);
const exec = async (newParams: P) => {
setIsLoading(true);
try {
const response = await queryFn(newParams || params);
setData(response);
} catch (error) {
setError(error as Error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
exec(params);
}, []);
return { data, error, isLoading, exec };
};
export default useQuery;
src\useTestQuery.ts
import useQuery, { QueryResult } from "./useQuery";
type Params = { a: number; };
type Response = { b: number; };
// 模拟请求
const getData = (params: Params): Promise
// 使用请求的hooks服务 const useTestQuery = (params: Params): QueryResult<Response, Params> => { return useQuery<Response, Params>(getData, params); };
export default useTestQuery;
>`src\App.tsx`
```tsx
import { useEffect, useState } from "react";
import useTestQuery from "./useTestQuery";
import "./App.css";
function App() {
const { data, error, isLoading, exec } = useTestQuery({ a: 10 });
useEffect(() => {
setTimeout(() => {
exec({ a: 20 });
}, 10000);
}, []);
return (
<>
{isLoading && <div>加载中...</div>}
{error && error.message}
{!isLoading && data && data?.b}
</>
);
}
export default App;
加载,10秒后显示20,20秒后显示40.
interface Props<T> {
list: T;
}
interface Return {}
export const useSwipeHandler = <T>({ list }: Props<T>): Return => {
return {};
};
在 TypeScript 中,可以为这个自定义 Hook 添加类型定义,以确保传入的变量名称是一个字符串,并且返回的变量值也是一个字符串。以下是带有 TypeScript 类型的实现:
import { useState, useEffect } from 'react';
function useCSSVariable(variableName: string): string {
const [variableValue, setVariableValue] = useState<string>('');
useEffect(() => {
const root = document.documentElement;
// 获取 CSS 变量的当前值
const getCSSVariableValue = (): string => {
return getComputedStyle(root).getPropertyValue(variableName).trim();
};
// 初始化变量值
setVariableValue(getCSSVariableValue());
// 创建 MutationObserver 实例来监视变量变化
const observer = new MutationObserver(() => {
setVariableValue(getCSSVariableValue());
});
// 监视根元素的属性变化
observer.observe(root, { attributes: true, attributeFilter: ['style'] });
// 清理观察者
return () => observer.disconnect();
}, [variableName]);
return variableValue;
}
export default useCSSVariable;
与之前相同,只是在 TypeScript 文件(.tsx
)中使用:
import React from 'react';
import useCSSVariable from './useCSSVariable';
const App: React.FC = () => {
const mainColor = useCSSVariable('--main-color');
return (
<div>
<p>The main color is: {mainColor}</p>
</div>
);
};
export default App;
variableName: string
:接受一个字符串参数作为 CSS 变量名称。string
:表示返回的 CSS 变量的当前值是一个字符串。
hooks