React Element/组件/JSX/TSX

  React是一个JavaScript的UI库,通过Javascript来构建UI(用户界面(创建Html元素)),但只需要描述DOM长什么样子,React会帮你创建DOM。在JS中,描述一个东西通常使用对象,有哪些属性,值是什么。

const h1Elem = {
    type: 'h1',
    props: { id: 'recipe', children: ['Hello World'] }
}

  h1Elem对象也称React Element。这种方法太累,可以提供一个方法,返回一个对象(工厂模式)。React提供了createElement(后来是Jsx())来创建对象,接受三个参数,type(什么类型元素),  properties(元素有哪些属性),children(元素有哪些子元素)

const h1Elem = React.createElement('h1', {id: 'recipe'}, 'Hello World');

  这么写还是有点费劲,DOM本来就是根据html元素创建的,可不可以用HTML的方式描述DOM,在JS中写HTML?这就是JSX(JS + XML),对JS语法进行了扩展,可以在JS中写xml/html语法。

<h1 id='recipe'>Hello World</h1>

  html中的标签(类型)、属性和children正好对应createElement中的type,属性和children三个参数。JSX就是createElement函数的调用,返回值就是React Element,所以看到JSX就是看到一个普通的JS对象。整个Web应用不可能写到一个JSX中,把一部分JSX封装到一个函数中并返回,就形成了函数组件,

const Content = () => {
    return <h1 id='recipe'>Hello World</h1>
}

  函数组件也是描述DOM,只不过是描述一段DOM, 相当于自定义了一个类型,普通HTML类型怎么用,它就怎么用,<Content ></Content>,语法统一。不过,为了区分,自定义类型(函数组件名)要大写,还要注意返回一个单一的根节点,一个类型一个根节点。函数组件有个好处,就是能接受参数,但怎么传递参数?HTML/XML元素只能定义属性,<Content title="Hello World"></Component>。React会把所有属性封装到一个对象中,传递给函数组件,

const Content = (props) => {
    return <h1 id='recipe'>{props.title}</h1>
}

  所以函数组件只有一个参数props。

  JSX是对JS语法进行的扩展,它始终都是JS语法(写在.js文件中),所以JS中的所有语法,它都支持,JS中所有要求,它也必须遵守。XML中支持JS,引入了{}语法,{}里面的内容都当作JS表达式,进行求值。XML中符合JS要求,不能使用JS的保留字和关键字。XML元素经常添加class属性,但class在js中是关键字,class要变成className. 还有for,要用htmlFor,<label htmlFor=”input” className=”text”></label>。还有style属性,必须是一个对象, 所有的样式都是字符串

const inlineStyle = {
    color: 'pink',
    WebkitFilter: 'blur(5px)' //  -wibkit-filter
}
return <ul style={inlineStyle}> ...</ul>

  JSX是JS的扩展,浏览器并不支持该扩展,需要把它转换成JS,这就需要babel或esbuid等编译工具。JSX结合TS,就有了TSX。在tsx文件中,可以声明类型。为了支持TSX, tsconfig中要配置 "jsx": "react-jsx"。使用类型的第一个地方是props,

type PropsType = {
  isDisabled: boolean;
  onClick: (e: React.MouseEvent<HTMLButtonElement>) => void; // react 是合成事件,事件的类型是React中的类型
  style: React.CSSProperties;
  children: React.ReactNode;
}

  组件接受children,它的类型还有另一种写法,PropsWithChildren<T>类型,自动添加children属性,其他非children的属性,写到它的泛型里面。

import { type PropsWithChildren } from "react";
interface HeadingProps { isLarge: boolean }

function Heading(props: PropsWithChildren<HeadingProps>) {}

  有时组件接受的属性是html元素的原有属性,比写一个Image组件,<img>元素接受src,width等,React提供了ComponentProps<T>和ComponentPropsWithoutRef<T>类型,区别就是一个能接受ref,一个不能。ComponentPropsWithoutRef<"img"> 就获取到了img的所有类型。如果不想要所有类型,可以omit 或pick。

interface RegularImageProps extends ComponentPropsWithoutRef<"img"> {}
// 或
interface RegularImageProps extends Omit<ComponentPropsWithoutRef<"img">, "alt"> {}

  假设有一个第三方组件,

import { Rating } from "@mui";
<Rating value={9.2} max={10} icon="💕" />

  怎么获取Rating的props的类型?type of Rating 返回{value ...} => JSX.element,正好是Component组件类型,所以放到ComponentPropsWithoutRef中获取它的props类型。type RatingProps = ComponentPropsWithoutRef <typeof Rating>;

  extends一个类型时,可以限制类型,比如,父类型是可选的,子类型是必选的,父类型可以多个类型,子类型只能一个类型。比如input类型的value是可选的,并且值可以是number和string, 就可以限制必选,且是string

import { ComponentPropsWithoutRef } from "react";
interface StringInput extends ComponentPropsWithoutRef<'input'> {
  value: string
}

  但是一个一个列出,就比较麻烦,

import { ComponentPropsWithoutRef } from "react";

type ButtonProps = ComponentPropsWithoutRef<"button">

type RequireSome<T, K extends keyof T> = T & Required<Pick<T,K>>
type HoverButton = RequireSome<ButtonProps, "onMouseOver" | "onMouseMove" | 'onMouseOut'>

  放松类型,只能继承的时候,omit 某一个类型,然后在手动添加这个类型。

import { ComponentPropsWithoutRef } from "react";
interface AnyInput extends Omit<ComponentPropsWithoutRef<'input'>, "value" >{
  value?: string | boolean
}

  获取原生html元素的类型,还可以使用React.HtmlProps<T>。html元素的类型都是HtmlElement,再具体一点就是HtmlInputElement, HtmlDivEelemt。事件也是一样,最基本的事件类型就是Event,再具体一点就是changeEvent,哪个元素类型的changeEvent<T>

  其次是useState和useReducer的状态类型。useState<类型>就可以,useReducer和reducer函数有关,reducer函数第一个参数是状态,第二个参数是action,所以,userReducer返回的状态的类型就是reducer函数参数状态的类型,返回的dispacth的类型,React.Dispatch<action的类型>。action 通常是一个对象,有type, payload。type一般都是类型的交集, 比如 "ADD" | "SUB", playload就是携带的数据类型,比如type Action = {type: "ADD" | "SUB", playload: number}。

type Action = { type: "ADD" | "SUB", playload: number };
const reducer = (state: {count: number}, action: Action) => { return state }

function App() {
  const [state, disptch] = useReducer(reducer, {count: 0})
  return <div>{state.count}</div>
}

  React Context的类型好定义,type ContextType= {count: number; setState: (value: number) => void} 。但创建Context的时候, 需要默认值, const context = CreateContext<ContextType>(),  可以写一个没有用的setState,更简单的办法是使用as。const context = createContext<ContextType>({} as ContextType)。

  当组件A接受一个组件B作为参数时,A组件的参数类型就是React.componentType<B组件的接受的属性的类型>

interface StatusProps {
  status: 'Loaded' | 'Loading';
}

// A component that takes another component as a prop
const WithLoadingStatus = ({ Component }: { Component: ComponentType<StatusProps> }) => {
  // ... logic to determine status
  const status = 'Loaded' as const; 
  
  return <Component status={status} />;
};

  useRef, 它current 是什么类型,useRef就是什么类型,ref 指向input,useRef<HTMLInputElement>(null),ref指向一个值,useRef<{name: string} | null>(null)   

 

posted @ 2017-04-20 19:32  SamWeb  阅读(26404)  评论(0)    收藏  举报