Skip to content

React和TS学习

React + Ts 学习

准备工作

  • 安装nodejs,16 往上都是可以的

  • 使用国内的npm镜像,镜像地址

    使用方式:

    bash
    $ npm install -g cnpm --registry=https://registry.npmmirror.com
  • 编辑器使用vscode

  • 安装git进行代码版本管理

创建项目

资料

create-react-app 中文官网

vitejs 官网

使用 create-react-app 创建项目

bash
npx create-react-app my-app

加上--template typescript使用typescript语言模板。

启动项目

bash
npm start

停止项目

bash
ctrl c

使用 vite 创建项目

bash
npm create vite@latest my-vue-app --template vue

默认使用模板为vue,可以改成react-ts

bash
npm create vite@latest react-demo-vite --template react-ts
  • 选择React框架
  • 选择Typescript
  • 进入项目目录,npm install
  • 启动项目: npm run dev

最终选择

选择create-react-app

  • React官网推荐
  • 时间更久,可查找资源更多
  • 稳定性较强,求稳不求新

使用 prettier 来规范代码的风格

安装插件

进入项目控制台

bash
npm install prettier eslint-config-prettier eslint-plugin-prettier eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react --save-dev

创建配置文件

创建一个.eslintrc.js文件进行配置

js
module.exports = {
  root: true,
  env: {
    browser: true,
    es2021: true,
    node: true,
  },
  extends: [
    "eslint:recommended",
    "plugin:react/recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
  ],
  overrides: [],
  parser: "@typescript-eslint/parser",
  parserOptions: {
    esmaVersion: "latest",
    sourceType: "module",
  },
  plugins: ["react", "@typescript-eslint"],
  rules: {},
};

vscode里安装prettier格式化插件

配置.vscode/settings.json在代码保存的时候自动格式化

json
{
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  }
}

最后配置.prettierrc.js

js
module.exports = {
  // 箭头函数只有一个参数的时候可以忽略括号
  arrowParens: "avoid",
  // 括号内部不要出现空格
  bracketSpacing: true,
  // 行结束符使用 unix 格式
  endOfLine: "lf",
  jsxBracketSameLine: false,
  // 行宽
  printWidth: 100,
  // 换行方式
  proseWrap: "preserve",
  // 分号
  semi: false,
  // 使用单引号
  singleQuote: true,
  // 缩进
  tabWidth: 2,
  // 使用 tab 缩进
  useTabs: false,
  // 后置逗号,多行对象,数组在最后一行加逗号
  trailingComma: "es5",
  parser: "typescript",
};

如果遇到啥无效的时候,重启 vscode 尝试

创建命令并使用

json
"scripts": {
        "start": "react-scripts start",
        "build": "react-scripts build",
        "test": "react-scripts test",
        "eject": "react-scripts eject",
        "lint": "eslint 'src/**/*.+(ts|ts|jsx|tsx)' ",
        "format": " prettier --write 'src/**/*.+(ts|ts|jsx|tsx)' "
    }
bash
# 这样就可以直接格式化对应的代码
npm run format

提交到 git 仓库

选择仓库

  • 工作中:使用公司内部git仓库
  • 正式的开源项目,需要积累star,使用github
  • 个人学习项目,建议选择国内平台,coding.net
  • 配置好仓库之后将本地代码添加并推送到远程仓库

使用 husky 来规范流程

  • 一个git hook工具
  • git commit之前执行自定义的命令
  • 在提交代码之前执行代码风格的检查,避免提交非规范代码
  • github 地址

安装 husky

bash
npm install husky -D

使用

bash
npm pkg set scripts.prepare="husky install"
npm run prepare

添加一个 hook

我们使用npm run lint来检测代码风格,使用npm run format来将上一步检测的代码进行重新格式化,最后如果上一步成功了,然后将代码添加到本地仓库。

bash
npx husky add .husky/pre-commit "npm run lint"
npx husky add .husky/pre-commit "npm run format"
npx husky add .husky/pre-commit "git add ."

可以看到.husky/pre-commit文件内容

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npm run lint
npm run format
git add .

commit-lint

前提是安装了 husky

github 地址

安装(我是 mac,对应的 github 官网地址又对应的文档区分)

bash
npm install --save-dev @commitlint/{config-conventional,cli}

配置

bash
echo "module.exports = {extends: ['@commitlint/config-conventional']}" > commitlint.config.js

添加 hook

bash
npx husky add .husky/commit-msg  'npx --no -- commitlint --edit ${1}'

.husky/commit-msg

shell
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx --no -- commitlint --edit ${1}

此时会在提交的commit -m 里的描述进行检测是否规范。

这样提交的信息例如:

bash
git commit -m "chore: commit-lint"

这样才能通过规范。

主要有这么些可选type

'build',
'chore',
'ci',
'docs',
'feat',
'fix',
'perf',
'refactor',
'revert',
'style',
'test',

vite 和 webpack 的区别

  • webpack 是一个非常流行的前端打包工具
  • Create-React-App 内部使用的是 webpack 进行打包
  • Vite 即是构建工具,又是打包工具
  • webpack 会把目录下的 js 都合并到一个文件里进行打包

Vite 的特点

  • Vite 比 Create-React-App 打包项目更快(启动、代码更新)
  • Vite 使用了 ES Module 的语法(仅开发环境)
  • 打包方式,可以在script引入的地方加上一个type="module"<script src="./js/main.js" type="module"></script>

JSX 和组件

JSX 语法

  • JSX- JS 的扩展,卸载 JS 代码里,组件的 UI 结构
  • 语法和 HTML 很相似
  • 已成为 ES 规范(非 React 独有),可用于其他框架,如 Vu3

文件格式:

  • js
  • jsx
  • ts
  • tsx

标签

基本和 HTML 的标签差不多,区别如下:

  • 大小写,小写就是 HTML 标签,大写是自定义组件
  • 标签必须要闭合,如<input> 没有/jsx中是非法的
  • 每一片段只能有一个根节点,避免多个组合到一起回产生垃圾标签,还有一个<></>这种方式来替换

属性

也和 HTML 的属性差不多,也有一些区别:

  • class要换成className,因为在ts或js语法中class都是一个类关键字,这个时候就不能使用class

  • style要使用 JS 对象,不能是string,而且key要使用驼峰写法,例如:<a style={{backgroundColor: "red"}}></a>

  • for要改为htmlFor,因为还是关键字,不是很常用,例如:

    jsx
    <div>
      <label htmlFor="username">姓名</label>
      <input id="username" />
    </div>

事件

  • 使用onXxx的形式
  • 必须传入一个函数(是fn而非fn())
  • 注意Typescript类型(初学不深究)
tsx
import React from "react";
import type { MouseEvent } from "react";
import logo from "./logo.svg";
import "./App.css";

function App() {
  const fn = (event: MouseEvent<HTMLButtonElement>) => {
    // 阻止默认行为
    event.preventDefault();
    // 阻止冒泡
    event.stopPropagation();
    console.log("clicked");
  };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
          style={{ backgroundColor: "red" }}
        >
          Learn React edit
        </a>
        <div>
          <button onClick={fn}>click</button>
        </div>
      </header>
    </div>
  );
}

export default App;

Typescript类型

练习官网

typescript
function print<T>(info: T) {
  console.log(info);
}

// 给这个 T 传了一个 string info 就是 string 类型
print<String>("hello");

对于学习 Java 的后端来说,这个在 Java 里是泛型的意思。看使用这个函数的人传递什么类型的。

ts
class Foo {
  info: string;
  setInfo(newInfo: string) {
    this.info = newInfo;
  }
}

const f1: Foo = new Foo();
f1.setInfo("hello world");

react中可以使用import type {} from 'react'来获取类型,内置的不用获取。


多参数传递调用

tsx
import React from "react";
import type { MouseEvent } from "react";
import logo from "./logo.svg";
import "./App.css";

function App() {
  const fn = (event: MouseEvent<HTMLButtonElement>, name: string) => {
    // 阻止默认行为
    event.preventDefault();
    // 阻止冒泡
    event.stopPropagation();
    console.log(name);
  };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
          style={{ backgroundColor: "red" }}
        >
          Learn React edit
        </a>
        <div>
          <button
            onClick={(event) => {
              fn(event, "无解"); // 里面还是函数
            }}
          >
            click
          </button>
        </div>
      </header>
    </div>
  );
}

export default App;

插入 JS 变量

前面的代码demo里都有一个外置的大括号

tsx
<a
  className="App-link"
  href="https://reactjs.org"
  target="_blank"
  rel="noopener noreferrer"
  style={{ backgroundColor: "red" }}
></a>

{ backgroundColor: 'red' }这一段本身是 JS 对象,而外面的大括号就是用于可插入 JS 函数以及其他内容。

  • 使用{xxx}可以插入 JS 变量、函数、表达式
  • 可插入普通文本、属性
  • 可用于注释: {/* <p>{logo}</p> */},注释必须闭合的

条件判断

通常都是使用if else elseif来判断;在jsx/tsx里使用方式:

  • 使用 &&
  • 使用三元表达式
  • 使用函数
tsx
const flag = true;

function Hello() {
  if (flag) {
    return <p>Hello</p>;
  } else {
    return <p>你好</p>;
  }
}

return (
  <div>
    <div>
      {flag && <p>Hello</p>}
      {flag ? <p>Hello</p> : <p>你好</p>}
      <Hello></Hello> {/* 标签首字母大写,自定义组件 */}
    </div>
  </div>
);

循环

  • 使用数组
  • 每个item元素需要key属性
  • key在同级别需要唯一性
tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";

function App() {
  const list = [
    { username: "无解", name: "wujie" },
    { username: "张三", name: "zhangsan" },
    { username: "李四", name: "lisi" },
  ];
  return (
    <div className="App">
      <header className="App-header">
        <ul>
          {list.map((user, index) => {
            const { username, name } = user;
            return (
              <li key={username} data-index={index}>
                {name}
              </li>
            );
          })}
        </ul>
      </header>
    </div>
  );
}

export default App;

注意不能使用index作为key,虽然简短的代码看不出问题,后续修改的时候可能不会唯一

代码演练:开发列表页

tsx
import React from "react";
import "./App.css";

function App() {
  // 列表页

  // 问卷列表数组
  const questionList = [
    { id: 1, title: "问卷 1", isPublished: true },
    { id: 2, title: "问卷 2", isPublished: false },
    { id: 3, title: "问卷 3", isPublished: true },
    { id: 4, title: "问卷 4", isPublished: false },
  ];

  function edit(id: number) {
    console.log("edit", id);
  }
  return (
    <div>
      <h1>问卷列表页</h1>
      <div>
        {questionList.map((question) => {
          const { id, title, isPublished } = question;
          return (
            <div key={id}>
              <strong>{title}</strong> &nbsp;
              {/* 条件判断 */}
              {isPublished ? (
                <span style={{ color: "green" }}>已发布</span>
              ) : (
                <span>未发布</span>
              )}
              &nbsp;
              <button
                onClick={() => {
                  edit(id);
                }}
              >
                编辑问卷
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
}

export default App;

组件

React 一切皆是组件

  • 组件就是一个 UI 片段
  • 拥有独立的逻辑和显示
  • 组件可大可小,可嵌套

组件的价值和意义

  • 组件嵌套来组织 UI 结构,和 HTML 一样,无学习成本
  • 组件拆分,利于代码维护,和多人协作开发
  • 可封装公共组件(或第三方组件)复用代码,提高开发效率

组件形式

  • class组件
  • 函数组件
  • React16 之后比较推崇函数组件和 Hooks

将前面的列表代码封装成一个组件独立出去

List1.tsx

tsx
import React, { FC } from "react";
import "./List1.css";

const List1: FC = () => {
  // 问卷列表数组
  const questionList = [
    { id: 1, title: "问卷 1", isPublished: true },
    { id: 2, title: "问卷 2", isPublished: false },
    { id: 3, title: "问卷 3", isPublished: true },
    { id: 4, title: "问卷 4", isPublished: false },
  ];

  function edit(id: number) {
    console.log("edit", id);
  }
  return (
    <div>
      <h1>问卷列表页</h1>
      <div>
        {questionList.map((question) => {
          const { id, title, isPublished } = question;
          return (
            <div key={id} className="list-item">
              <strong>{title}</strong> &nbsp;
              {/* 条件判断 */}
              {isPublished ? (
                <span style={{ color: "green" }}>已发布</span>
              ) : (
                <span>未发布</span>
              )}
              &nbsp;
              <button
                onClick={() => {
                  edit(id);
                }}
              >
                编辑问卷
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );
};

export default List1;

App.tsx

tsx
import React from "react";
import List1 from "./List1";

function App() {
  return (
    <>
      <List1 />
    </>
  );
}

export default App;

React 开发者工具,chrome 插件

百度搜索:chrome react dev tools

谷歌下载地址

国内访问可以通过bing去搜索,如果有资源可以上网,那就使用谷歌搜索出来的

JSX 和 Vue 模板的区别

  • 判断,Vue 模板中使用v-if指令,JSX 使用三元表达式,函数和&&
  • 循环,Vue 模板中使用的是v-for指令,JSX 使用map函数
  • React - 能用 JS 的就用 JS 的,需要 JS 基础比较扎实的使用
  • Vue - 简单使用iffor就可以方便初学者使用和记忆
  • 现在 Vue3 也能很好的支持 JSX 语法,同时还是继承前面的指令

React Hooks

  • React 内置的 Hooks
    • useState
    • useEffect
    • 其他内置
  • 自定义 Hooks (复用代码)
  • 使用第三方 Hooks(提高效率)

注意事项

  • Hooks 是 React 最重要的内容
  • 必须练习,练习,练习!
  • Hooks 有很对规则,遇到错误时,先查看是否违反规则

useState

需求:让页面“动”起来

  • 点击一个button,累加数量
  • 用普通变量,无法实现
  • 使用useState实现
  • 用普通的 JS 变量无法触发组件的更新
tsx
import React, { useState } from "react";

function App() {
  // let count = 0
  const [count, setCount] = useState(0);

  function add() {
    setCount(count + 1);
  }
  return (
    <>
      <div>
        <button onClick={add}>add {count}</button>
      </div>
    </>
  );
}

export default App;

useState设置返回的结果是一个数组,第一个元素是这个变量,第二个是改变这个变量的函数,必须传入的是一个新值。

state一个组件的“独家记忆”

  • props父组件传递过来的信息
  • state组件内部的状态信息,不对外
  • state变化,触发组件更新,重新渲染rerender页面

上面代码中,count相当于statestate的变化就相当于setCount方法。

state的几个特点

  • 异步更新: 无法拿到最新的state的值

    tsx
    import React, { FC, useState } from "react";
    
    const Demo: FC = () => {
      const [count, setCount] = useState(0);
    
      function add() {
        // setCount(count + 1)
        setCount((count) => count + 1);
        console.log("cur count", count);
      }
    
      return (
        <div>
          <button onClick={add}>add {count}</button>
        </div>
      );
    };
    
    export default Demo;
  • 注意,如果设置的一个变量没有在tsx/jsx里引用,按理说就不需要使用setstate来管理它,使用useRef

  • 可能会被合并

    tsx
    import React, { FC, useState } from "react";
    
    const Demo: FC = () => {
      const [count, setCount] = useState(0);
    
      function add() {
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        setCount(count + 1);
        console.log("cur count", count);
      }
    
      return (
        <div>
          <button onClick={add}>add {count}</button>
        </div>
      );
    };
    
    export default Demo;

    上面每次都是0+1,所以不会打印 5,也可以使用函数的方式来更新,就会是 5

    tsx
    import React, { FC, useState } from "react";
    
    const Demo: FC = () => {
      const [count, setCount] = useState(0);
    
      function add() {
        setCount((count) => count + 1);
        setCount((count) => count + 1);
        setCount((count) => count + 1);
        setCount((count) => count + 1);
        setCount((count) => count + 1);
        console.log("cur count", count);
      }
    
      return (
        <div>
          <button onClick={add}>add {count}</button>
        </div>
      );
    };
    
    export default Demo;

    使用函数更新 state 不会被合并

  • 不可变数据

    tsx
    import React, { FC, useState } from "react";
    
    const Demo2: FC = () => {
      const [userInfo, setUserInfo] = useState({ name: "无解", age: 12 });
    
      function changeAge() {
        // 不可变数据
        // 不去修改 state 的值,而是传入一个新的值
        // ES6 解构语法
        setUserInfo({ ...userInfo, age: 21 });
      }
    
      const [list, setList] = useState(["x", "y"]);
    
      function addItem() {
        // 不可变数据
        // push 返回的是插入数据的位置
        // list.push('z')
        // concat 返回的是一个新数组
        setList(list.concat("z"));
        // 解构赋值语法
        setList([...list, "z"]);
      }
    
      return (
        <div>
          <h2>state 不可变数据</h2>
          <div>{JSON.stringify(userInfo)}</div>
          <button onClick={changeAge}>change age</button>
          <div>{JSON.stringify(list)}</div>
          <button onClick={addItem}>add item</button>
        </div>
      );
    };
    
    export default Demo2;

重构列表页

jsx
import React, { FC, useState } from 'react'
import QuestionCard from './components/QuestionCard'

const List2: FC = () => {
  const [questionList, setQuestionList] = useState([
    { id: '1', title: '问卷 1', isPublished: true },
    { id: '2', title: '问卷 2', isPublished: false },
    { id: '3', title: '问卷 3', isPublished: true },
    { id: '4', title: '问卷 4', isPublished: false },
  ])

  function add() {
    // 随机数
    const r = Math.random().toString().slice(-3)
    setQuestionList(
      questionList.concat({
        id: 'q' + r,
        title: '问卷5' + r,
        isPublished: false,
      })
    )
  }

  function deleteQuestion(id: string) {
    // state 是不可变的数据
    // 删除如何操作 使用 filter 过滤掉 id 对应的数据
    setQuestionList(
      questionList.filter(q => {
        if (q.id === id) return false
        else return true
      })
    )
  }

  function publishQuestion(id: string) {
    // 修改使用 map
    setQuestionList(
      questionList.map(q => {
        // 不等于要修改的直接返回
        if (q.id !== id) return q
        return {
          ...q,
          isPublished: true,
        }
      })
    )
  }

  return (
    <div>
      <h2>问卷列表 2</h2>
      <div>
        {questionList.map(question => {
          const { id, title, isPublished } = question
          return (
            <QuestionCard
              key={id}
              id={id}
              title={title}
              isPublished={isPublished}
              deleteQuestion={deleteQuestion}
              publishQuestion={publishQuestion}
            />
          )
        })}
      </div>
      <div>
        <button onClick={add}>新增问卷</button>
      </div>
    </div>
  )
}

export default List2

组件QuestionCard

jsx
import React, { FC } from 'react'

// ts 自定义类型
type PropsType = {
  id: string
  title: string
  isPublished: boolean
  // 函数类型 可选属性类型
  deleteQuestion?: (id: string) => void
  publishQuestion?: (id: string) => void
}

const QuestionCard: FC<PropsType> = props => {
  const { id, title, isPublished, deleteQuestion, publishQuestion } = props

  function pub(id: string) {
    // 组件的状态提升
    publishQuestion && publishQuestion(id)
  }

  function del(id: string) {
    // 需要通知父组件进行删除
    // 执行父组件的删除操作
    deleteQuestion && deleteQuestion(id)
  }

  return (
    <div key={id} className="list-item">
      <strong>{title}</strong>
      {isPublished ? <span style={{ color: 'green' }}>已发布</span> : <span>未发布</span>}
      <button
        onClick={() => {
          pub(id)
        }}
      >
        发布问卷
      </button>
      &nbsp;
      <button onClick={() => del(id)}>删除</button>
    </div>
  )
}

export default QuestionCard

使用 immer

  • state是不可变数据
  • 操作成本高,有很大的不稳定性

就可以使用immer可以避免这些问题

安装

bash
npm install immer --save

ImmerDemo.tsx

tsx
import React, { FC, useState } from "react";
import { produce } from "immer";

const Demo: FC = () => {
  const [userInfo, setUserInfo] = useState({ name: "无解", age: 12 });
  const [list, setList] = useState(["x", "y"]);

  function changeAge() {
    // setUserInfo({ ...userInfo, age: 21 })
    setUserInfo(
      // 可以直接修改某个字段
      produce((draft) => {
        draft.age = 21;
        draft.name = "无解的游戏";
      }),
    );
  }

  function addItem() {
    setList(
      produce((draft) => {
        draft.push("z");
      }),
    );
  }

  return (
    <div>
      <h2>state 不可变数据</h2>
      <div>{JSON.stringify(userInfo)}</div>
      <button onClick={changeAge}>change age</button>
      <div>{JSON.stringify(list)}</div>
      <button onClick={addItem}>add item</button>
    </div>
  );
};

export default Demo;

现在修改我们的内容都可以使用js的一些特有的方法和习惯。

最近更新