React18 Hooks工作笔记

827 阅读8分钟

Hooks + Router.6路由 + Redux状态管理 + Axios封装

1.Create React App 搭建项目

创建react项目,需要在电脑上安装 Node >= 8.10 和 npm >= 5.6 终端执行:

npx create-react-app my-app --template typescript // 创建的 TypeScript 项目
npx create-react-app my-app // 默认创建的 javaScript 项目
cd my-app
npm start

编辑器常用缩写

// ### `imrse`
import React, { useState, useEffect } from 'react' 
// ### `imr`
import React from 'react'
// ### `rfce` [创建新页面、用于父文件]
import React from 'react'

function $1() {
    return <div>$0</div>
}

export default $1
// ### `rafc` [创建组件、用于子组件]
import React from 'react'

export const index = () => { // index 名字自定义 开头大写 例:MenuTop
    return (
        <div>
            
        </div>
    )
}
// 简写 `thenc` [then成功、catch失败、finally最后(成功失败都会走这里)]
.then((result) => {

}).catch((err) => {

}).finally(() => {

})
// 简写 nfn
const name = (params) => {
    
}

2.工程目录

工程目录截图

别人的目录,我要改成自己的

├── README.md
├── package-lock.json
├── package.json
├── public
│   └── index.html
├── src
│   ├── api                    # 封装axios
│   │   └── request.ts         # 封装axios
│   ├── assets                 # 静态资源
│   ├── components             # 通用业务组件[no.1创建]
│   ├── context.ts             # 全局配置
│   ├── declaration.d.ts
│   ├── history.ts             # history 实例
│   ├── index.tsx              # 入口文件
│   ├── layout                 # 布局
│   ├── locale                 # 国际化语言包
│   ├── mock                   # 模拟数据
│   ├── pages                  # 页面模板
│   ├── redux                  # 状态管理中心
│   ├── routes.tsx             # 路由配置
│   ├── settings.json          # 配置文件
│   ├── style                  # 全局样式
│   └── utils                  # 工具库[no.2创建]
└── tsconfig.json

.env 文件 自动化切换环境变量

.env // 全局配置,npm run build [打包生产📦] 走这个url地址
.env.development // 开发环境配置 npm start [开发本地运行📦] 走这个url地址
.env.production // 生产环境配置 npm run build [打包生产] 走这个url地址
// **优先级:** 若有相同配置项时,环境配置项会覆盖 > 全局配置项
// 只需要创建 .env 和 .env.development 就好

image.png

REACT_APP_API_URL=http://localhost:3001
import React, { useEffect } from 'react'

const apiUrl = process.env.REACT_APP_API_URL // 引入 .env文件 [配置url地址]
export default function Index() {
    useEffect(() => {
        fetch(`${apiUrl}/report`)
    }, [])
    return (
        <div>0</div>
    )
}

React 基础语法

Hooks优点

  1. hooks不在需要statethis来管理类组件。hooks可以取代三个生命周期函数,抛弃componentWillMountcomponentWillReceivePropscomponentWillUpdate
  2. hooks可以把一切功能,都用函数组件来开发,函数组件有着较好的复用性,拼装拆分自己想要的组件。并且hooks多了一个参数,来控制渲染,可以避免函数组件重复执行的副作用
  3. 也不在需要redux来管理组件。

7大常见のHooks

B站视频.# Hooks 入门教程案例——小野森森

B站视频.# 9分钟掌握React Hooks正确认知——魔术师卡颂

1.useState

最基本也是最重要的useState用来定义自变量。

2.useEffect

有副作用的因变量 给没有生命周期的组件,添加结束渲染信号。函数组件结束渲染的回调,useEffect在render之后结束回调

3.useMemo 4.useCallback

无副作用的因变量

5.useReducer

为了方便操作更多的自变量有了useReducer

6.useContext

跨组件层级操作自变量有了useContext可以替代redux的功能

7.useRef

让组件变得更加灵活useRef

2.react组件间の通信方式

父子组件间的数据传递

父组件

import React, { useState } from 'react'
import Test from './components/Test'
function App() {
  const [title, setTitle] = useState("旧数据")
  // 父向子传数据
  const onClickTitle = () => {
    setTitle("荔枝海盐口味")
  }
  // 子向父传数据
  const onClickTitle_Two = (params) => {
    setTitle(params)
  }
  return (
    <div className="App">
      <Test title={title} onClickTitle={onClickTitle} onClickTitle_Two={onClickTitle_Two}/>
    </div>
  );
}

export default App;

子组件

import React from 'react'

function Index({ title, onClickTitle, onClickTitle_Two }) {
    return (
        <div>
            <h1>{title}</h1>
            <button onClick={onClickTitle}>父向子传数据</button>
            <button onClick={() => onClickTitle_Two("外星人电解质水")}>子向父传数据</button>
            {/* 写箭头函数的原因,点击事件之后才执行这个匿名函数,如果不写箭头函数。就直接执行了 */}
        </div>
    )
}

export default Index

1.useState

useState用来定义自变量。
之前的类组件是用this.statethis.setState({})来初始化以及更新类组件的状态
函数组件是使用useState来定义初始化以及更新状态的函数组件。

2121545-20210820174958774-229889225.png

计数器

image.png image.png

import React, { useRef } from 'react'

function Index({ addItem }) {
    const inputRef = useRef(null) // 声明
    const submitValue = () => {
        const inputValue = inputRef.current.value.trim() // trim() 删除头尾空白
        if (inputValue.length === 0) {
            return
        }
        addItem(inputValue) // 把数据传出去
        // inputValue.current.value = '' // 清空表单
    }

    return (
        <div>
            <input type="text" ref={inputRef} />
            <button onClick={submitValue}>添加</button>
        </div>
    )
}

export default Index

todolist增删小案例【Hook版本】

import React, { useState, useEffect } from 'react'
export default function Index() {
    const [inputValue, setInputValue] = useState('')
    const [list, setList] = useState([
        { name: 'React-Router6.0.0' },
        { name: 'React-Redux' },
    ])
    const add = () => {
        const newList = [...list]
        newList.push({ name: inputValue })
        setList(newList)
    }
    const del = (index) => {
        console.log('index', index);
        const newList = [...list]
        newList.splice(index, 1)
        setList(newList)
    }
    return (
        <div>
            <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
            <button onClick={add}>查询</button>
            <ul>
                {
                    list.map((item, index) => { 
                        return (
                            <li key={index} onClick={() => del(index)}>{item.name}</li>
                        )
                     })
                }
            </ul>
        </div>
    )
}

todolist小案例【父子传值版本】

index.jsx父组件

import React, { useState, useEffect } from 'react'
import { List } from './list';
export default function Index() {
    const [inputValue, setInputValue] = useState('')
    const [list, setList] = useState([
        { name: 'React-Router6.0.0' },
        { name: 'React-Redux' },
    ])
    const add = () => {
        const newList = [...list]
        newList.push({ name: inputValue })
        setList(newList)
    }
    const del = (index) => {
        console.log('index', index);
        const newList = [...list]
        newList.splice(index, 1)
        setList(newList)
    }
    return (
        <div>
            <input type="text" value={inputValue} onChange={(e) => setInputValue(e.target.value)}/>
            <button onClick={add}>查询</button>
            <List list={list} del={del} />
        </div>
    )
}

list.jsx子组件

export const List = ({ list, del }) => {
    return (
        <ul>
            {
                list.map((item, index) => {
                    return (
                        <li key={index} onClick={() => del(index)}>{item.name}</li>
                    )
                })
            }
        </ul>
    )
}

table 表格查询小案例

bababa

4.Axios

数据模拟 json-server

优点: 配置简单30秒启动,增删改查真实模拟

// 安装
yarn global add json-server // json-server 需要全局安装
npm install -g json-server
// 启动 json-server
json-server src/JsonServer/data.json --watch --port 3001

image.png

GET /tickets // 列表
GET /tickets/12 // 详情
POST /tickets // 增加
PUT /tickets/12 // 替换
PATCH /tickets/12 // 修改
DELETE /tickets/12 // 删除

fetch()

3.1封装axios

axios请求拦截器和响应拦截器

let whitelist = ['/login'] // 拦截器白名单,登录时不添加 token
// 请求拦截
axios.interceptors.request.use(
 config => {
  // 拦截白名单以及添加token
  if (whitelist.includes(config.url)) {
   return config
  } else {
   let token = localStorage.token
   config.headers.token = token
   return config
  }
 },
 err => {
  return err
 }
)

// 响应拦截
axios.interceptors.response.use(
 response => {
  return response
 },
 err => {
  // 添加超时处理
  let isTimeout = err.toString().includes(`timeout of ${axios.defaults.timeout}ms exceeded`)
  if (isTimeout) {
   return Promise.reject(Message.error('请求超时,请重新尝试'))
  }
  // 添加404和500
  switch (err.response.status) {
   case 404:
    return Promise.reject(Message.error('服务器中查找不到该资源'))
   case 500:
    return Promise.reject(Message.error('请求失败,服务端错误,请重新尝试'))
  }
  return Promise.reject(err)
 }
)

useDebounce防抖

防抖:触发高频事件后n秒内函数只会执行一次,如果n秒内高频事件再次被触发,则重新计算时间。

function useDebounce(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (current.timer) {
      clearTimeout(current.timer);
    }
    current.timer = setTimeout(() => {
      current.fn.call(this, ...args);
    }, delay);
  }, dep)
}

useThrottle节流

节流:高频事件触发,但在n秒内只会执行一次,所以节流会稀释函数的执行频率。

function useThrottle(fn, delay, dep = []) {
  const { current } = useRef({ fn, timer: null });
  useEffect(function () {
    current.fn = fn;
  }, [fn]);

  return useCallback(function f(...args) {
    if (!current.timer) {
      current.timer = setTimeout(() => {
        delete current.timer;
      }, delay);
      current.fn.call(this, ...args);
    }
  }, dep);
}

备注:createRef 每次渲染都会返回一个新的引用,而 useRef 每次都会返回相同的引用。

每次组件重新渲染,都会执行一遍所有的hooks,这样debounce高阶函数里面的timer就不能起到缓存的作用(每次重渲染都被置空)。timer不可靠,debounce的核心就被破坏了。使用useRef的目的就是为了解决这个问题。

useRef实现了React组件的缓存机制。

7. React—Router6.0.0 路由

请说出React Router的一些优点

怎么去传值,怎么去匹配路径

页面路由

window.location.href = 'http://www.baidu.com'

Hash路由

image.png

window.location.href = "#test1"

H5路由

Link

<Link to={{ pathname: `/assetDetails`, search: '?&assetType=' + item.code  }}>
    <Button>带参数跳转,link和<a>标签一样</Button>
</Link>

8.Redux状态管理模式

1_redux安装

yarn add redux
yarn add react-redux // 专门为react设计的高阶组件,主要是提供了一个方法connext。将react和我们的组件链接在一起
yarn add redux-thunk // thunk[θʌŋk] 异步的时候用的
yarn add axios qs // 序列化的qs,咱也不懂

// package.json 文件
"devDependencies": {
  "redux": "^4.0.5",
  "react-redux": "^7.1.1",
  "redux-thunk": "^2.3.0"
}

2_redux 基本操作

import { createStore } from 'redux';
// 1.store  存放数据的 "数据仓库"
// 2.state  具体数据 [state存放在store里面]
// 3.action 对象 描述当前是如何操作 state 状态的
const action_AddOne = {
  type: 'Add',
  num: 1
}
const action_Addtwo = {
  type: 'Add',
  num: 2
}
const action_Square = {
  type: 'Square',
}

// 4.reducer 是一个函数
const reducer_A = (state = 10, action) => {
  switch (action.type) {
    case 'Add':
      return state + action.num
    case 'Square':
      return state * state
    default:
      return state
  }
}

const store = createStore(reducer_A);
// 5.store.getState() // 去store 找对应的 state
console.log(store.getState()) // 10
// 5.dispatch  是能够更改 state 的唯一方法
// 使用公式:store.dispatch(action) // 公式大意: 告诉 store 我们是以怎样的 action 来更改 state 的值
console.log(store.dispatch(action_AddOne)) // {type: 'Add', num: 1}
console.log(store.getState()) // 10 + 1
console.log(store.dispatch(action_Addtwo)) // {type: 'Add', num: 2}
console.log(store.getState()) // 11 + 2
console.log(store.dispatch(action_Square)) // {type: 'Square'}
console.log(store.getState())// 13 * 13

3_提取action&提取reducer

4_数据在页面显示

B站视频.# Redux 入门教程案例——小夏

7.redux-thunk

Redux中文文档地址

Redux的设计思想很简单,就俩句,请牢记

(1) web应用是一个状态机,视图与状态是一一对应的
(2) 所有的状态,保存在一个对象里面

5.TS应用强类型

Ts是强类型,有更强的类型约束,如要数字类型就只能传 10,字符串“10”就不行。而Js这种弱类型就可以, image.png 静态类型:声明时确定变量类型就不允许改变 动态类型:变量类型随便发生变化

image.png

TS原始类型

6.生命周期

React v16.8的生命周期

抛弃componentWillMount、componentWillReceiveProps、componentWillUpdate,三个生命周期函数

React v16.0前的生命周期

生命周期函数:指在某一时刻,会自动执行的函数

生命周期分为四大部分 初始化。加载。更新。卸载 初始化

    constructor(props) {
      super(props)

      this.state = {
         inputValue: '',
         list: ['a','b','c','b'],
    }

加载 页面初次加载时,只执行一次的生命周期函数

    render() {
        console.log('页面加载:render,页面加载时执行')
    }

    componentWillMount() {
        console.log('页面加载:componentWillMount,页面即将加载的时候,在render之前调用')
    }
    //例如:在页面加载之前,看看用户有没有登录啊

    componentDidMount() {
        console.log('页面加载:componentDidMount,(装载完成)在render之后调用')
    }
    //例如: 在页面加载,加载一些Ajax网络的请求,3秒倒计时,进入首页啊

数据更新

start数据更新是有3个生命周期函数
props子组件数据更改时有4个函数 多一个componentWillReceiveProps函数写在子组件里

shouldComponentUpdate(nextProps, nextState) {
    if(nextProps.content !== this.props.inputValue) {
        return true;
    }else {
        return false;
    }
}
// 做性能优化的生命周期函数
// true就是要更新
// false就是不要更新

componentWillUpdate() {
    console.log('数据更新:如果shouldComponentUpdate返回ture,则执行componentWillUpdate、如果shouldComponentUpdate返回false,则不执行componentWillUpdate') 
}


componentDidUpdate() {
    console.log('数据更新:在组件数据更新之后,她会自动执行componentDidUpdate')
}


componentWillReceiveProps() {
    console.log('数据更新:componentWillReceiveProps函数只能在子组件里执行,执行条件1.改子组件接收了父组件参数、2.父组件的render函数执行,子组件这个函数才能执行')
}
// props子组件数据更改时使用

卸载

componentWillUnmount() {
    console.log('卸载:componentWillUnmount组件即将卸载时,执行')
}
// 清理无效timer;取消未完成的网络请求;清理已注册的订阅

Antd工作遇到的问题

Table表格[拖拽调整列宽]

react+antd实现Table拖拽调整列宽,这种功能案例在antd 3.x版本里面可以找到,安装4.x版本也同样支持的。需要集成 react-resizable 来实现可伸缩列。

import React, { Component } from 'react'
import { Table} from 'antd';
import { Resizable } from 'react-resizable';
import './App.css';
export class App extends Component {

  constructor(props) {
    super(props)
  
    this.state = {
      columns: [
        { title: 'Date', dataIndex: 'date', width: 200},
        { title: 'Amount', dataIndex: 'amount', width: 100 },
        { title: 'Type', dataIndex: 'type', width: 100 },
        { title: 'Note', dataIndex: 'note', width: 100 },
        { title: 'Action', key: 'action', render: () => <a>Delete</a> },
      ],
      dataSource: [
        { key: 0, date: '2018-02-11', amount: 120, type: 'income', note: 'transfer' },
        { key: 1, date: '2018-03-11',  type: 'income', note: 'transfer' },
        { key: 2, date: '2018-04-11', amount: 98, type: 'income', note: 'transfer' },
      ]
    }
  }
  

  ResizeableTitle = props => {
    const { onResize, width, ...restProps } = props;
  
    if (!width) {
      return <th {...restProps} />;
    }
  
    return (
      <Resizable
        width={width}
        height={0}
        onResize={onResize}
        draggableOpts={{ enableUserSelectHack: false }}
      >
        <th {...restProps} />
      </Resizable>
    );
  };

  components = {
    header: {
      cell: this.ResizeableTitle,
    },
  };

  handleResize = index => (e, { size }) => {
    this.setState(({ columns }) => {
      const nextColumns = [...columns];
      nextColumns[index] = {
        ...nextColumns[index],
        width: size.width,
      };
      return { columns: nextColumns };
    });
  };

  render() {
    const columns = this.state.columns.map((col, index) => ({
      ...col,
      onHeaderCell: column => ({
        width: column.width,
        onResize: this.handleResize(index),
      }),
    }));

    return ( 
      <Table bordered components={this.components} columns={columns} dataSource={this.state.dataSource} />
    )
  }
}

export default App
.react-resizable {
	position: relative;
	background-clip: padding-box;
}

.react-resizable-handle {
	position: absolute;
	width: 10px;
	height: 100%;
	bottom: 0;
	right: -5px;
	cursor: col-resize;
	z-index: 1;
}

Table表格[对某一列数据排序]

image.png

const columns = [
    { title: '省份名称', dataIndex: 'AAA', key: 'AAA', sorter: (a, b) => a.AAA.localeCompare(b.AAA) }, // "北京"
    { title: '省份编码', dataIndex: 'BBB', key: 'BBB', sorter: (a, b) => a.BBB - b.BBB }, // "10100"
    { title: '插入时间', dataIndex: 'CCC', key: 'CCC', sorter: (a, b) => moment(a.CCC, 'YYYY-MM-DD HH:mm:ss') - moment(b.CCC, 'YYYY-MM-DD HH:mm:ss') }, // "2020-11-06T07:44:42.000+0000"
]
<Table columns={columns} />