React开发规范及技巧

React

命名规范

  • 组件名称: MyComponent推荐使用大驼峰命名
  • 属性名称: onClick React DOM 使用小驼峰命令来定义属性的名称,而不使用 HTML 属性名称的命名约定
  • style 样式属性: backgroundColor 采用小驼峰命名属性的 JavaScript 对象
  • className名称: test-main__content--blue BEM命名规范(块:是什么、元素:是什么、修饰符:出外形行为状态等特征)

文件夹文件名

类型 规范 示例
图片文件夹 小写开头驼峰 src/assets/image/xxxXxx
图片文件 小写中划线 xxx-xxx.png
公共组件components 大写开头驼峰 src/components/XxxXxx/index.tsx
src/components/XxxXxx/index.less
页面文件 大写开头驼峰 src/pages/Xxx/index.tsx
src/pages/Xxx/index.less
src/pages/Xxx/components/XxxXxx/index.tsx
全局状态 小写开头驼峰 src/store/xxxXxx.ts
常量 小写开头驼峰 src/constants/xxxXxx.ts
类型定义 小写开头驼峰 src/types/xxxXxx.d.ts
公共方法 小写开头驼峰 src/utils/xxxXxx.ts

单文件行数限制

单个文件最大不能超过500行,超过后需要拆分组件

函数组件和 class 类组件的使用场景

建议优先使用函数组件+hooks,组件开发

组件的代码顺序

组件应该有严格的代码顺序,这样有利于代码维护,我们推荐每个组件中的代码顺序一致性。

class Example extends Component {
    // 静态属性
    static defaultProps = {}
 
    // 构造函数
    constructor(props) {
        super(props);
        this.state={}
    }
 
    // 声明周期钩子函数
    // 按照它们执行的顺序,部分钩子”过时”,不建议再使用。
    // 1. componentWillMount(过时)
    // 2. componentWillReceiveProps(过时)




    // 3. getDerivedStateFromProps
    // 4. shouldComponentUpdate
    // 5. getSnapshotBeforeUpdate
    // 6. componentDidMount
    // 7. componentDidUpdate
    // 8. componentWillUnmount
    
    componentDidMount() { ... }
 
    // 事件函数/普通函数
    handleClick = (e) => { ... }
 
    // 最后render 方法
    render() { ... }
}

函数组件应将处理相同功能的代码,抽离到独立hook中,供组件使用

{
    // 1. useRef
    // 2. useState
    // 3. useXxxHooks
    // 4. useEffect 优先空依赖 - deps:[]
    // 5. react其他内置hook
    
    // 事件函数/普通函数,在useEffect外最好用useCallBack包一下
    const handleClick = (e) => { ... }
    

    // 最后返回render内容
}

跨组件通信

  • 状态提升:如果多个组件需要反映相同的变化数据,建议将共享状态提升到最近的共同父组件中去;从而依靠自上而下的数据流,而不是尝试在不同组件间同步 state

  • 「组件」推荐使用 Context,与项目降低耦合度:如果某个属性在组件树的不同层级的组件之间需要用到,我们应该使用 Context 提供在组件之间共享此属性的方式,而不是显式地通过组件树的逐层传递 props

  • 「页面」推荐使用 Redux:统一状态(与server交互数据、页面组件交互数据)管理,以业务模块作为命名空间划分,eg: auth权限, user用户 等。

Typescript

组件引入方式

函数组件

// 推荐使用✅ better
const WrapComponent: React.FC<ExtendedProps> = (props) => {
 // return ...
};




// 直接使用
export default WrapComponent;
// 或者
export default function (props: React.PropsWithChildren<SpinProps>) {
 // return ...
}

类组件

type IEProps {
 Cp?: React.ComponentClass<{ id?: number }>;
}

type IEState { id: number; }
//推荐使用✅ better
class ClassCpWithModifier extends React.Component<IEProps, IEState> {
 private gid: number = 1;
 public state: Readonly<IEState> = { id: 1 };
 render() { return this.state.id = 2; } // ts(2540)
}


两者均可使用的类型

React.ComponentType<P> = React.ComponentClass<P> | React.FunctionComponent<P>;

Element

onClick and onChange

  // click 使用 React.MouseEvent 加 dom 类型的泛型
  // HTMLInputElement 代表 input标签 另外一个常用的是 HTMLDivElement
  const onClick = (e: React.MouseEvent<HTMLInputElement>) => {};✅ better
  // onChange使用 React.ChangeEvent 加 dom 类型的泛型
  // 一般都是 HTMLInputElement,HTMLSelectElement 可能也会使用
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {};✅ better
  return (
    <>
      {'ProForm 设置泛型可以约定 onFinish 等接口的参数类型'}
      <ProForm<DataType> />
      {`
       DataType 设置render 中行的类型,
       Params 是参数的提交类型
       ValueType 表示自定的 valueType 类型,ProTable 会自动进行合并
     `}
      <ProTable<DataType, Params, ValueType> />
      <input onClick={onClick} onChange={onChange} />
    </>
  );


Forms and onSubmit

import * as React from 'react'




type changeFn = (e: React.FormEvent<HTMLInputElement>) => void


const App: React.FC = () => {


  const [state, setState] = React.useState('')



  const onChange: changeFn = e => {
    setState(e.currentTarget.value)
  }
  // ✅ better
  const onSubmit = (e: React.SyntheticEvent) => {
    e.preventDefault()
    const target = e.target as typeof e.target & {
      password: { value: string }
    } // 类型扩展
    const password = target.password.value
  }


  return (
    <form onSubmit={onSubmit}>
      <input type="text" value={state} onChange={onChange} />
    </form>
  )


}

Hooks 使用

useState

//给定初始化值情况下可以直接使用




import { useState } from 'react';
// ...
const [val, toggle] = useState(false);
// val 被推断为 boolean 类型
// toggle 只能处理 boolean 类型





// 没有初始值(undefined)或初始 null





type AppProps = { message: string };
const App = () => {
    const [data] = useState<AppProps | null>(null); // ✅ better
    // const [data] = useState<AppProps | undefined>();
    return <div>{data?.message}</div>;
};

setState 的更新可能是异步的

useEffect

function DelayedEffect(props: { timerMs: number }) {
    const { timerMs } = props;


    useEffect(() => {
        const timer = setTimeout(() => {
            /* do stuff */
        }, timerMs);
        
        // 可选
        return () => clearTimeout(timer);
    }, [timerMs]);
    // ✅ 确保函数返回 void 或一个返回 void|undefined 的清理函数
    return null;
}






//异步请求,处理方式:


// ✅ better
useEffect(() => {
    (async () => {
        const { data } = await ajax(params);
        // todo
    })();
}, [params]);


// 或者 then 也是可以的
useEffect(() => {
    ajax(params).then(({ data }) => {
        // todo
    });
}, [params])

useRef

function TextInputWithFocusButton() {
    // 初始化为 null, 但告知 TS 是希望 HTMLInputElement 类型
    // inputEl 只能用于 input elements
    const inputEl = React.useRef<HTMLInputElement>(null);
    const onButtonClick = () => {
        // TS 会检查 inputEl 类型,初始化 null 是没有 current 上是没有 focus 属性的
        // 你需要自定义判断! 
        if (inputEl && inputEl.current) {
            inputEl.current.focus();
        }
        // ✅ best
        inputEl.current?.focus();
    };
    return (
        <>
            <input ref={inputEl} type="text" />
            <button onClick={onButtonClick}>Focus the input</button>
        </>
    );
}

useReducer

使用 useReducer 时,多多利用 Discriminated Unions 来精确辨识、收窄确定的 type 的 payload 类型。 一般也需要定义 reducer 的返回类型,不然 TS 会自动推导。

const initialState = { count: 0 };




// ❌ bad,可能传入未定义的 type 类型,或码错单词,而且还需要针对不同的 type 来兼容 payload
// type ACTIONTYPE = { type: string; payload?: number | string };




// ✅ good
type ACTIONTYPE =
    | { type: 'increment'; payload: number }
    | { type: 'decrement'; payload: string }
    | { type: 'initial' };





function reducer(state: typeof initialState, action: ACTIONTYPE) {
    switch (action.type) {
        case 'increment':
            return { count: state.count + action.payload };
        case 'decrement':
            return { count: state.count - Number(action.payload) };
        case 'initial':
            return { count: initialState.count };
        default:
            throw new Error();
    }
}

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <>
            Count: {state.count}
            <button onClick={() => dispatch({ type: 'decrement', payload: '5' })}>-</button>
            <button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
        </>
    );
}

useContext

一般 useContext 和 useReducer 结合使用,来管理全局的数据流。

interface AppContextInterface {
    state: typeof initialState;
    dispatch: React.Dispatch<ACTIONTYPE>;
}









const AppCtx = React.createContext<AppContextInterface>({
    state: initialState,
    dispatch: (action) => action,
});
const App = (): React.ReactNode => {
    const [state, dispatch] = useReducer(reducer, initialState);

    return (
        <AppCtx.Provider value={{ state, dispatch }}>
            <Counter />
        </AppCtx.Provider>
    );
};

// 消费 context
function Counter() {
    const { state, dispatch } = React.useContext(AppCtx);
    return (
        <>
            Count: {state.count}
            <button onClick={() => dispatch({ type: 'decrement', payload: '5' })}>-</button>
            <button onClick={() => dispatch({ type: 'increment', payload: 5 })}>+</button>
        </>
    );
}

自定义 Hooks

Hooks 的美妙之处不只有减小代码行的功效,重点在于能够做到逻辑与 UI 分离。做纯粹的逻辑层复用。 例子:当你自定义 Hooks 时,返回的数组中的元素是确定的类型,而不是联合类型。可以使用 const-assertions 。

//例子:当你自定义 Hooks 时,返回的数组中的元素是确定的类型,而不是联合类型。可以使用 const-assertions 。




export function useLoading() {
    const [isLoading, setState] = React.useState(false);
    const load = (aPromise: Promise<any>) => {
        setState(true);
        return aPromise.finally(() => setState(false));
    };
    return [isLoading, load] as const; // 推断出 [boolean, typeof load],而不是联合类型 (boolean | typeof load)[]
}







//也可以断言成 tuple type 元组类型。



export function useLoading() {
    const [isLoading, setState] = React.useState(false);
    const load = (aPromise: Promise<any>) => {
        setState(true);
        return aPromise.finally(() => setState(false));
    };
    return [isLoading, load] as [
        boolean, 
        (aPromise: Promise<any>) => Promise<any>
    ];
}




//如果对这种需求比较多,每个都写一遍比较麻烦,可以利用泛型定义一个辅助函数,且利用 TS 自动推断能力。

function tuplify<T extends any[]>(...elements: T) {
    return elements;
}


function useArray() {
    const numberValue = useRef(3).current;
    const functionValue = useRef(() => {}).current;
    return [numberValue, functionValue]; // type is (number | (() => void))[]
}

function useTuple() {
    const numberValue = useRef(3).current;
    const functionValue = useRef(() => {
    }).current;
    return tuplify(numberValue, functionValue); // type is [number, () => void]
}

泛型

当函数的入参和返回值都应该可以是任意类型时

function fn<T>(arg: T): T {
  return arg
}



// 或


const fn1: <T>(arg: T) => T = arg => {
  return arg
}

Partial 对象或接口属性变为可选

type Person = {




  name:string,




  age:number



}





type p = Partial<Person>
//  type p = {




//    name?: string;
//    age?: number;
//  }


Required对象或接口属性变为必选

type Person = {




  name:string,




  age?:number
}





type p = Required<Person>
//  type p = {




//    name: string;

//    age: number;
//  }


Pick 提取类型的部分属性

type Person = {




  name:string,




  age:number



}





type A = Pick<Person,"name" | "xxx">
//  type p = {




//    name: string;

//  }

Omit 忽略类型的部分属性

type Person = {




  name:string,




  age:number



}





type A = Omit<Person,"name" | "xxx">
//  type p = {




//    age: string;
//  }

Readonly所有属性设为只读

type Person = {




  name:string,




  age:number



}





type A = Readonly<Person>
//  type p = {




//    readonly name: string;
//    readonly age: number;
//  }


Exclude从联合类型中去除指定的类型

Extract从联合类型中提取指定的类型

NonNullable从联合类型中去除 null 或者 undefined 的类型

Record泛型约束

Type 和Interface的选用时机

  • 在定义公共 API 时(比如编辑一个库)使用 interface,这样可以方便使用者继承接口

  • 在定义组件属性(Props)和状态(State)时,建议使用 type,因为 type的约束性更强

  • type 类型不能二次编辑,而 interface 可以随时扩展

其他技巧

对象类型尽量使用Record<string, unknown> 代替{} 和object

type ObjectTypes = {
    objBetter: Record<string, unknown>; // ✅ better,代替 obj: object
    
    // 对于 obj2: {}; 有三种情况:
    obj2Better1: Record<string, unknown>; // ✅ better 同上
    obj2Better2: unknown; // ✅ any value
    obj2Better3: Record<string, never>; // ✅ 空对象
    

    /** Record 更多用法 */
    dict1: {
        [key: string]: MyTypeHere;
    };
    dict2: Record<string, MyTypeHere>; // 等价于 dict1
};




//好处:


//1.当你书写 home 值时,键入 h 常用的编辑器有智能补全提示;
//2.home 拼写错误成 hoem,会有错误提示,往往这类错误很隐蔽;
//3.收窄接收的边界。

函数类型不建议直接给 Function 类型,有明确的参数类型、个数与返回值类型最佳

type FunctionTypes = {
    onSomething: Function; // ❌ bad,不推荐。任何可调用的函数
    onClick: () => void; // ✅ better ,明确无参数无返回值的函数
    onChange: (id: number) => void; // ✅ better ,明确参数无返回值的函数
    onClick(event: React.MouseEvent<HTMLButtonElement>): void; // ✅ better
};

React Prop 类型

export declare interface AppProps {
    children1: JSX.Element; // ❌ bad, 没有考虑数组类型
    children2: JSX.Element | JSX.Element[]; // ❌ 没考虑字符类型
    children3: React.ReactChildren; // ❌ 名字唬人,工具类型,慎用
    children4: React.ReactChild[]; // better, 但没考虑 null
    children: React.ReactNode; // ✅ best, 最佳接收所有 children 类型
    functionChildren: (name: string) => React.ReactNode; // ✅ 返回 React 节点
    

    style?: React.CSSProperties; // React style
    

    onChange?: React.FormEventHandler<HTMLInputElement>; // 表单事件! 泛型参数即 `event.target` 的类型
}

使用查找类型访问组件属性类型

// Great
import Counter from './d-tips1'


type PropsNew = React.ComponentProps<typeof Counter> & {




  age: number


}


const App: React.FC<PropsNew> = props => {





  return <Counter {...props} />



}


export default App

Promise 类型


type IResponse<T> = {
  message: string
  result: T
  success: boolean
}


async function getResponse(): Promise<IResponse<number[]>> {
  return {
    message: '获取成功',
    result: [1, 2, 3],
    success: true,
  }
}






getResponse().then(response => {
  console.log(response.result)
})

typeof/instanceof/in/is: 类型守卫用于类型区分

//typeof
function doSome(x: number | string) {
  if (typeof x === 'string') {
    // 在这个块中,TypeScript 知道 `x` 的类型必须是 `string`
    console.log(x.subtr(1)); // Error: 'subtr' 方法并没有存在于 `string` 上
    console.log(x.substr(1)); // ok
  }



  x.substr(1); // Error: 无法保证 `x` 是 `string` 类型
}







//instanceof



class Foo {
  foo = 123;
  common = '123';
}
class Bar {
  bar = 123;
  common = '123';
}
function doStuff(arg: Foo | Bar) {
  if (arg instanceof Foo) {
    console.log(arg.foo); // ok
    console.log(arg.bar); // Error
  }
  if (arg instanceof Bar) {
    console.log(arg.foo); // Error
    console.log(arg.bar); // ok
  }
}

doStuff(new Foo());
doStuff(new Bar());

//in

interface A {
  x: number;
}

interface B {
  y: string;
}

function doStuff(q: A | B) {
  if ('x' in q) {
    // q: A
  } else {
    // q: B
  }
}

//is

function isString(test: any): test is string{
    return typeof test === 'string';
}

function example(foo: number | string){
    if(isString(foo)){
        console.log('it is a string' + foo);
        console.log(foo.length); // string function
    }
}
example('hello world');
// is 为关键字的「类型谓语」把参数的类型范围缩小了,当使用了 test is string 之后,我们通过 isString(foo) === true 明确知道其中的参数是 string,而 boolean 并没有这个能力,这就是 is 关键字存在的意义.

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYNq2SHE' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片