React个人学习记录(持续更新...)

248 阅读4分钟

当前React版本为17.0.2,课程源自慕课网React 17 系统精讲 结合TS打造旅游电商平台 轻松掌握大厂前端核心技能

全局安装React脚手架

npm install -g create-react-app

初始化React项目

  1. npx create-react-app my-app
  2. cd my-app
  3. npm start

React暴露webpack配置

npm run eject / yarn eject

React默认把webpack相关的配置隐藏起来,如果需要更改webpack配置,需要执行相关命令。并且,暴露webpack配置的操作为不可逆(即暴露之后不可隐藏回去)。

初始化React-TypeScript项目

npx create-react-app react-ts-demo -template typescript

react-ts-demo为项目名称

React-JavaScript迁移为React-TypeScript

安装以下npm包,成功之后会生成ts.config.js文件。

npm install --save typescript @types/node @types/react @types/react-dom @types/jest

配置ReactCSS模组

一般情况下,css文件与component文件放在同一个目录下,并且使用同样的命名方式。

1.
// 直接引入整个css文件 (容易有全局样式污染)
import './index.css'
<div className="app" />

2.
// css模块化引入组件
import style from './index.css'
<div className={style.app} />

css模组化引入需要额外的ts配置

src路径下新建custom.d.ts文件,写进以下代码:

declare module "*.css" {
    const css: { [key: string]: string };
    export default css;
}
  • *.d.ts (.d.ts文件会自动被ts识别)
  • 只包含类型声明 不包含逻辑
  • 不会被编译 也不会被webpack打包

既然配置了css作为模块加载,那么对应的css文件名也应该以module为后缀。需要将css文件名都改为xx.module.css,同时,在引入的位置也需要更新一下。

这种命名规则是css模块化的约定规范,配合typesciprt最好使用这种命名约定,否则,等会可能会出现一些意外。

vscode添加css模组化智能提示

  1. npm i typescript-plugin-css-modules --save-dev
  2. 在tsconfig.json中加入配置
"plugins": [{"name": "typescript-plugin-css-modules"}]
  1. 在项目根文件下新增.vscode文件夹,在文件夹中新增settings.json文件,加入以下配置
{
    "typescript.tsdk": "node_modules/typescript/lib",
    "typescript.enablePromptUseWorkspaceTsdk": true
}

React支持模块化引入的媒体资源类型

/node_modules/react-script/lib/ract-app.d.ts文件中,声明了react支持import的文件类型。

StateProps

区别:

  • props是组件对外的接口,state是组件对内的接口
  • props是用于组件间数据传递,state是用于组件内部数据传递

State:

  • 类组件中constructor是唯一可以初始化state的地方
  • 不可直接修改state,必须通过setState修改state中的属性的值
  • setState的更新是异步的

Props:

  • props是只读的

React事件绑定绑定this指向的方法

  1. 在构造函数中绑定
constructor() {
    this.handleClick = this.handleClick.bind(this);
}
  1. onClick事件中使用箭头函数绑定
onClick={() => this.handleClick()}
  1. onClick中使用bind绑定
onClick={this.handleClick.bind(this)}
  1. class初始化时将事件函数定义为箭头函数
handleClick = () => {
    console.log('this is:', this);
}

onClick={this.handleClick}

TS省略any类型声明的小技巧

tsconfig.json中添加以下配置

"noImplicitAny": false

对于setState异步更新和同步执行的理解

传送门

setState本身并不是异步,只是因为react的性能优化机制体现为异步。在react的生命周期函数或者作用域下为异步,在原生的环境下为同步。

React常用生命周期

  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount

Hook

Hook React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

useState:来自官网的demo

// 引入 React 中的 `useState` Hook
import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 count 的 state 变量,初始值为0 ,以及一个更新 count 的方法 setCount
  // useState 返回一个数组,采用数组解构的方式赋值给了 count ,setCount
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

useEffect

  1. 可以模拟componentDidMount、componentDidUpdate、componentWillUnmount这三个生命周期函数的使用场景
// 模拟 componentDidMount,第二个参数传 空数组 ,ui更新之后便执行回调方法
useEffect(() => {
    document.title = `点击${count}次`
}, [])

// 模拟 componentDidUpdate,第二个参数为数组,数组元素为需要追踪的 state 属性
// 一旦数组中有某个元素更新了,便会执行回调方法。
useEffect(() => {
    document.title = `点击${count}次`
}, [count])

// 模拟 componentWillUnmount,在 useEffect 第一个参数中 return 一个方法。
useEffect(() => {
    document.title = `点击${count}次`
    return () => {
        // 执行 useEffect 的清除逻辑
    }
}, [count])
  1. useEffect如果不传第二个参数会导致死循环,一直更新。

解决方法:第二个参数传一个空数组

  1. useEffect不接受返回一个promise,若要使用async,await,则需要把代码做一下包装:
const [robotGallery, setRobotGallery] = useState<any>([])
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<string>()

useEffect(() => {
    const fetcnData = async () => {
      setLoading(true)
      try {
        const res = await fetch("https://jsonplaceholder.typicode.com/users")
        const data = await res.json()
        setRobotGallery(data)
      } catch (error: any) {
        setError(error.message)
      }
      setLoading(false)
    }
    // 手动调用 fetcnData
    fetcnData()
}, [])

ContextuseContext

Context提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

// grandpa 组件
// 定义一个默认值
const defaultContextValue = {
  username: '菜菜菜菜'
}
// 调用 createContext 创建一个 Context
export const appContext = React.createContext(defaultContextValue)

ReactDOM.render(
  <React.StrictMode>
    {/* 使用创建的 appContext.Provider 包裹住要接收值的组件 */}
    <appContext.Provider value={defaultContextValue}>
      <App />
    </appContext.Provider>
  </React.StrictMode>,
  document.getElementById('root')
);

// father 组件不需操作

// grandson 组件
// 引入暴露出来的 appContext
import { appContext } from '../index'

// 使用 appContext.Consumer 包裹住要接收值的 jsx 代码
// 用 花括号 和 箭头函数 包裹住 jsx 代码
// { (value) = (jsx代码) }
const Robot : React.FC<RobotProps> = ({id, name, email}) => {
  return (
    <appContext.Consumer>
      {(value) => (
        <div className={styles.cardContainer}>
          <img alt='robot' src={`https://robohash.org/${id}`} />
          <h2>{name}</h2>
          <p>{email}</p>
          <p>作者:{value.username}</p>
        </div>
      )}
    </appContext.Consumer>
  )
}

使用hooksuseContext可以使我们写法更加简洁

// 引入 useContext
import React, { useContext } from 'react';

const Robot : React.FC<RobotProps> = ({id, name, email}) => {
  // 调用 useContext 
  const value = useContext(appContext)
  return (
    <div className={styles.cardContainer}>
      <img alt='robot' src={`https://robohash.org/${id}`} />
      <h2>{name}</h2>
      <p>{email}</p>
      {/* 接收 context 的值 */}
      <p>作者:{value.username}</p>
    </div>
  )
}

高阶组件HOC

  • 抽取重复代码,实现组件复用
  • 条件渲染,控制组件的渲染逻辑(渲染劫持)
  • 捕获/劫持被处理组件的声明周期

自定义Hook

  • Hook是函数
  • 命名以use开头
  • 内部可调用其他Hook函数
  • 并非React的特性

React电商项目实战

项目初始化

// 脚手架初始化 react + ts 项目
npx create-react-app react-mall -template typescript

// 安装开发环境依赖包,css模块化引入
npm i typescript-plugin-css-modules --save-dev

// tsconfig.json 添加配置
"noImplicitAny": false, // 不声明 any 类型不报错
"plugins": [ // css模块化插件
     {"name": "typescript-plugin-css-modules"}
]

// 根目录添加.vscode文件夹,文件夹中新建settings.json,添加以下配置
{
    "typescript.tsdk": "node_modules/typescript/lib",
    "typescript.enablePromptUseWorkspaceTsdk": true
}

模块化引用css

  1. App.css改为App.module.css
  2. App.tsx中修改为模块化引入,并修改使用位置
import styles from './App.module.css';

function App() {
  return (
    <div className={styles.App}>
      <header className={styles["App-header"]}>
        <img src={logo} className={styles["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"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

安装antd、@ant-design/icons组件库

npm i antd @ant-design/icons --save

全局引入antd-css文件遇到的问题

"react-scripts": "5.0.0"的版本为5.0.0时,按照antd官网引入css文件会报警告。该问题在ant-designissue中有人提及,传送门,报错如下图所示:

css.png

WARNING in ./node_modules/antd/dist/antd.css (./node_modules/css-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[1]!./node_modules/postcss-loader/dist/cjs.js??ruleSet[1].rules[1].oneOf[5].use[2]!./node_modules/source-map-loader/dist/cjs.js!./node_modules/antd/dist/antd.css)
Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map: 'webpack://antd/./components/icon/style/index.less' URL is not supported
 @ ./node_modules/antd/dist/antd.css 8:6-231 22:17-24 26:7-21 58:25-39 59:36-47 59:50-64 61:4-74:5 63:6-73:7 64:54-65 64:68-82 70:42-53 70:56-70 72:21-28 83:0-201 83:0-201 84:22-29 84:33-47 84:50-64
 @ ./src/index.tsx 8:0-28

解决方法:用 import 'antd/dist/antd.min.css'代替import 'antd/dist/antd.css'

模块化引入css如何在一个元素中添加两个类名

// 第一种
<div className=`${styles.['app-header']} ${styles['app-logo']}`></div>

// 第二种(推荐):https://www.npmjs.com/package/classnames
npm install classnames