react + ts项目从零开始(一)

1,522 阅读5分钟

typescript在前端开发中扮演了重要的角色,现在我们从零开始一个react + ts项目,一步一步,在项目中吧一些ts的用法做一些整理说明,希望对于大家在项目实战有所帮助,当然这是一个从基础开始的项目,同样在过程中去穿插一些ts语法讲解,我想将更多的重点放在ts项目中如何去使用,而不是干燥的去说ts类型语法,希望感兴趣的同学们可以收藏,我尽量保证一周一期

1、开始一个新项目

项目构建,这块我们直接使用create-react-app脚手架,在指定目录下执行npx create-react-app typescript --template typescript ,接下来我们就将当前的目录展示给大家,具体如下:
image.png
接下来对于目录public src等等这些文件夹及根目录下的文件的用途,我们不做说明,我们今天直说关于typescript相关的内容在react中的使用,接下来,我们将目标聚焦到react-app-env.d.ts,我们看一下下面这段代码:

/// <reference types="react-scripts" />

关于/// <reference types="react-scripts" />这块我们就需要说道历史包袱了,那么我们简单介绍一下,让我们了解ts早期的模块化写法:

  • 1、/// 是早期的ts模块化导入的方式,es6模块出来以后就不推荐使用了;
  • 2、reference有两个属性pathtypes,前者是用来import文件,后者是导入类型文件;

当我们使用/// <reference types="react-scripts" />,他会去node_modules目录下匹配找到对应库的类型,具体规则如下:

  • 1、查找node_modules/@types/react-scripts/index.d.ts类型声明文件;
  • 2、查找node_modules/@types/react-scripts/package.json文件中types指定的文件;
  • 3、查找node_modules/react-scripts/index.d.ts类型声明文件;
  • 4、查找node_modules/react-scripts/package.json文件中types指定的文件;

2、项目中使用css的基本方案

(1)全局css样式编写

我们初始化的项目中包含了一些全局样式(src/index.css),我们可以编写一些全局的样式;

(2)组件的样式

  • css文件(例如global样式文件),css-modules文件(我们可以编写xxx.module.css)我们可以讲得到的文件用模块的思维去给给组件className赋值
  • 通过style内联的方式配合 React.CSSProperties 方式进行组合,有了React.CSSProperties方式,vscode会有很良好的语法提示
import React from 'react';
import logo from './logo.svg';
import './App.css';

const buttonStyles: React.CSSProperties = {
  backgroundColor: '#fff',
  border: '1px solod #ccc',
  textAlign: 'center',
  lineHeight: '32px'
}

function App() {
  return (
    <div>
      <img src={logo} className="App-logo" alt="logo" />
      {/* 样式测试 */}
      <button style={buttonStyles}>测试</button>
    </div>
  );
}

export default App;
  • 使用外部的样式库,例如:tailwindcss、styled-component等等,这里我们用styled-components为例简单说一下基本使用办法(我们需要安装vscode-styled-components,让编译器有良好的提示功能)
yarn add styled-components
yarn add @types/styled-components -D

我们可以按照如下的方式使用

import React from 'react';
import logo from './logo.svg';
import './App.css';

import Styled from 'styled-components';

const buttonStyles: React.CSSProperties = {
  backgroundColor: '#fff',
  border: '1px solod #ccc',
  textAlign: 'center',
  lineHeight: '32px'
}

function App() {
  return (
    <AppContainer>
      <img src={logo} className="App-logo" alt="logo" />
      {/* 样式测试 */}
      <button style={buttonStyles}>测试</button>
    </AppContainer>
  );
}

const AppContainer = Styled.div`
background-color: #eee;
height: 100%;
`

export default App;

3、如何定义组件的props写法

通常情况下,我们可以通过type和interface两种方式去定义组件的props,大多数情况下两者是可以互换使用的,
如下所示:

import React from 'react'
import { AppBox } from './styles'

type ColumnProps = {
  text: string
}
// 或者
// interface  ColumnProps = {
//   text: string
// }

const Column: React.FC<ColumnProps> = ({ text }) => {
  return (
    <AppBox>
      {text || ''}
    </AppBox>
  )
}

export default Column

当然我们定义props字段也可以通过可选?方式定义,如下所示:

type ColumnProps = {
  text?: string
}
// 相当于 string | undefined,在我们组件中使用,需要做一层判断

4、如何定义组件 + Children属性

通常react组件我们可以向其内部插入Children去定制我们的组件,props中自带Children属性(使用vue的同学可以更简单的理解他更像是一个slot插槽的概念一样),那么我们如何用ts来去定义这样的props呢?具体如下几种方式:

  • 1、通过React.PropsWithChildren

    = P & { children?: React.ReactNode }

import { AppBox } from '../styles'

type ColumnProps = {
  text: string
}

type PropsInfo = React.PropsWithChildren<ColumnProps>

const Column: React.FC<PropsInfo> = ({ text, children }) => {
  return (
    <AppBox>
      {text || ''}
    </AppBox>
  )
}

export default Column
  • 2、可以通过type方式直接定义,当然我觉得这种方式会更加简单一些,具体如下:
type ColumnProps = {
  text: string.
  children?: React.ReactNode
}

4、关于useState

通常我们通过如下的方式定义一个state

const [show, setShow] = useState(false)

这样show的类型就会被解读为boolean类型,这种写法比较粗暴,我们解读一下useState的定义方式如下:

function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];

通过定义,我们可知,useState生成的数据类型我们可以通过泛型的方式去指定具体类型,如果没有指定它将会通过type S的方式自行去推导类型,所以我一直觉得最友好的的写法如下,可能麻烦一点,但是更明确一点

const [show, setShow] = useState<boolean>(false)

5、关于事件类型定义

如果我们想要对一个事件进行类型定义,具体的事件类型定义我们可以参考@types/react/index.d.ts内部, 此处我摘录部分事件的定义结合事件用法说明;

// Focus Events
onFocus?: FocusEventHandler<T> | undefined;
onFocusCapture?: FocusEventHandler<T> | undefined;
onBlur?: FocusEventHandler<T> | undefined;
onBlurCapture?: FocusEventHandler<T> | undefined;

// Form Events
onChange?: FormEventHandler<T> | undefined;
onChangeCapture?: FormEventHandler<T> | undefined;
onBeforeInput?: FormEventHandler<T> | undefined;
onBeforeInputCapture?: FormEventHandler<T> | undefined;
onInput?: FormEventHandler<T> | undefined;
onInputCapture?: FormEventHandler<T> | undefined;
onReset?: FormEventHandler<T> | undefined;
onResetCapture?: FormEventHandler<T> | undefined;
onSubmit?: FormEventHandler<T> | undefined;
onSubmitCapture?: FormEventHandler<T> | undefined;
onInvalid?: FormEventHandler<T> | undefined;
onInvalidCapture?: FormEventHandler<T> | undefined;

在我们实际开发过程中,如果需要在html元素添加事件,我们便可以通过如下的方式定义事件:

// 这块我们定义一个键盘输入事件  
const onKeyEnter: React.KeyboardEventHandler<HTMLInputElement> = e => {
  if(e.key !== 'Enter') {
    return
  }
  onAdd(text)
}

return (
  <NewItemFormContainer>
    {/* input元素 */}
    <NewItemInput
      value={text}
      ref={ref}
      onChange={(e) => setText(e.target.value)}
      onKeyDown={onKeyEnter}
    />
    <NewItemButton onClick={() => onAdd(text)}>
      create
    </NewItemButton>
  </NewItemFormContainer>
)

这块我们可以通过React.KeyboardEventHandler这种方式去定义事件,这样我们便可以有良好的提示
image.png
这期我们先写这么多,后续我继续从零开始ts项目中将一些类型使用加入进来,项目代码我上传至gitee码云上面,每章我都会打一个tag,仓库地址方便大家参考。