官网:https://react.docschina.org/
一、React简介
1. React入门
概述:React用于构建用户界面的轻量级的JS库,只关注 MVC 中的V(视图)。
特点:
2. React项目创建
2.1 官方脚手架
npx create-react-app 项目名
npm6+
npm init react-app 项目名
2.2 vite
npm create vite@last 项目名 -- --template react
2.3 redux
新项目:
npx create-react-app 项目名 --template redux
npx create-react-app 项目名 --template redux-typescript
老项目:
npm i redux react-redux
2.4 redux-thunk
npm i redux-thunk
2.5 index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>
|
2.6 路由
npm install react-router-dom
3. JSX语法
- 可以直接将标签赋值给变量
- 如果要在标签之间插入JS变量或者JS表达式,必须使用{}
- 需要替换部分的关键字,如标签上的 class 写为 className , for 写为 htmlFor
- 事件绑定要使用小驼峰的写法,如onClick
- 所有的标签必须闭合,即 必须写为
- 标签中的注释需要写在 {} 中
- 当变量是 Number,String 时可以直接显示
- 当变量是Null,Undefined,Boolean,就会显示空
- 如果是Object则不能直接渲染
- 如果是数组,那么会直接展开数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const title = '您好'; const head = <header>{title}</header>; {head}
const sum = () => 3; {sum()}
const title = '您好'; <h1 title={title}></h1>
const style = { color: 'red', backgroundColor: 'yellow' }; <h1 style={style}></h1>
<p className={n ? 'on' : ''}></p> <p className={['class1', 'class2', (n ? 'on' : '')].join(' ')}></p>
const dom = () => { if (flag) { return <p>flag 为 true</p> } else { return <p>flag 为 false</p> } }
flag ? <p>flag 为 true</p> : <p>flag 为 false</p>
flag && <p>flag 为 true</p>
const data = [ 'aa', 'bb', 'cc', 'dd' ] const newData = data.map((item, index) => <li key={index}>{item}</li>)
{newData}
|
4. 跨域
解决:
方法一:proxy
1 2 3
|
"proxy":"请求的地址" "proxy":"http://localhost:5000"
|
说明:
- 优点:配置简单,前端请求资源时可以不加任何前缀。
- 缺点:不能配置多个代理。
- 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)
方法二:创建代理配置文件
1
| 在src下创建配置文件:src/setupProxy.js
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| const proxy = require('http-proxy-middleware')
module.exports = function(app) { app.use( proxy('/api1', { target: 'http://localhost:5000', changeOrigin: true,
pathRewrite: {'^/api1': ''} }), proxy('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
|
说明:
- 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
- 缺点:配置繁琐,前端请求资源时必须加前缀。
5. React 和 Vue的区别
相同点:
不同点:
- 核心思想不通:
- Vue为渐进式框架,门槛较低,进行数据劫持或者代理
- React函数式编程,数据不可变以及单向数据流
- 组件写法差异:
- Vue单文件组建格式,即html,css,js写在同一个文件
- React为html和css都写进js中
- diff算法不同:
- react 首先对新集合进行遍历,通过唯一key值来判断新旧集合中是否存在相同的节点,如果没有的话就创建,如果有的话就会将节点在新集合中的位置和老集合中的位置进行比较,如果不同,则进行移动操作,否则就不操作。如果在遍历的过程中,发现在新集合中没有,就会进行删除操作。
- Vue新旧集合各有头尾的变量,他们的2各变量相互比较,一共有四种比较方式,如果四种都没匹配,则会匹配key值,借助key值找到可复用的节点,在进行相关操作。
- 响应式原理不同:
- Vue依赖收集,自动依赖,数据可变。递归监听data中的所有属性,直接修改,当数据改变时,自动找到引用组件重新渲染。
- React基于状态手动优化,数据不可变,需要setState驱动新的state替换老的state。当数据改变时,以组件为根目录,默认全部重新渲染,所以React中会需要shouldComponentUpdate这个生命周期函数来进行控制。
二、React核心
1. 组件化
概述:组件允许将 UI 部分拆违可独立使用的代码片段
组件划分:
class
组件:使用class 去创建组件,可以包含逻辑和状态- 函数组件:以函数为组件,目前可以为函数组件添加状态
1.1 class 组件
概述:每一个组件一个文件。该文件后缀可以是 js 也可以是 jsx
执行过程
- React解析组件标签,找到相应的组件
- 发现组建时类定义的,随后
new
出来的类的实例,并通过该实例调用到原型上的 render
方法 - 将
render
返回的虚拟 DOM 转化为真实的 DOM ,随后呈现在页面中
1.1.1 语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 写法一:在constructor 中添加的状态 class App extends React.Component { constructor(props) { super(props) this.state = {} } render() { return { <div> <h1>React</h1> </div> } } } export default App;
class App extends React.Component { state = {} constructor(props) { super(props); } render() {} } export default App;
|
1.1.2 修改数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
setState(updater[ ,callback]) * updater: 可以接受对象类型,也可以接受函数类型; * callback:可选参数,在 state 修改之后执行的回调函数;
this.setState({ count: 1 } this.setState({ count: this.state.count + 1 });
this.setState((preState, props) => ({ count: preState.count + 1 }))
|
局部数据修改
1 2 3 4 5 6 7 8
| const newUser = { ...this.state.user, name: 'lily' } this.setState({ user: newUser })
|
数组数据修改:
React 执行 diff 时是比较引用,直接修改源对象的值,引用值是不发生改变,React 无法检测到,就不会重新渲染,所以需要使用不可变对象(不直接改源对象,而是返回新对象)。即修改数组不能使用 push, splice 等等方法。
1 2 3
| this.setState({ arr: [...this.state.arr, 新值] })
|
1.1.3 注意:state
的更新是异步的
1 2 3 4 5 6
| changeC = () => { this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); this.setState({ count: this.state.count + 1 }); }
|
setState 方法通过一个队列机制实现 state 更新,当执行 setState 的时候,会将更新的 state 合并后放入队列,而不会立即更新。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 如果不想 setState 被合并,那么需要传递函数来实现:
changeC = () => { this.setState((preState) => ({ count: preState.count + 1 })) this.setState((preState) => ({ count: preState.count + 1 })) this.setState((preState) => ({ count: preState.count + 1 })) }
|
1.1.4 事件处理
绑定事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| export default class Counter extends Component { constructor(props) {
this.changeCount = this.changeCount.bind(this); } changeCount() {}
render() { return <div onClick={this.changeCount}></div> } }
export default class Counter extends Component { changeCount() {}
render() { return <div onClick={this.changeCount.bind(this)}></div> } }
export default class Counter extends Component { changeCount() {}
render() { return <div onClick={() => this.changeCount()}></div> } }
export default class Counter extends Component { changeCount = () => {}
render() { return <div onClick={this.changeCount}></div> } }
|
参数传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| export default class Counter extends Component { changeCount(str) {}
render() { return <div onClick={this.changeCount.bind(this, 'abc')}></div> } }
export default class Counter extends Component { changeCount(str) {}
render() { return <div onClick={() => this.changeCount('abc')}></div> } }
<button data-msg="hi" onClick={this.changeCount}></button>
changeCount = (e) => {
}
|
1.2 函数组件
概述:函数组件接受一个参数,该参数是父组件传递的props,组件内部必须返回 React 元素。
1 2 3 4
| function 组件名(props) { return () }
|
事件处理:
1 2 3 4 5 6 7 8 9 10 11
| function App(props) { function onHandler() { console.log('事件'); }
return ( <div> <button onClick={onHandler}>按钮</button> </div> ) }
|
2. 组件通信
2.1 props
概述:父组件传递给子组件的数据,子组件不能修改该数据,在 class
组件中通过 this.props
的方式获取。
使用:
1 2 3 4 5 6 7 8 9 10 11
| export default class Welcome extends Component { render() { return ( <div className='welcome'>{this.props.msg}</div> ) } }
<Welcome msg="Hi Props" />
|
peops约束:因为从 React 15.x 开始移除了 props 约束,所以需要安装第三方模块实现
nom i --save prop-types
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from 'react'; import PropTypes from 'prop-types'
export default class Welcome extends Component { render() { return ( <div className='welcome'>{this.props.msg}</div> ) } } Welcome.propTypes = { msg: PropTypes.string msg: PropTypes.string.isRequired }
|
2.2 子传父
1 2 3 4 5 6
| callback = (id) => {} <Button callback={callback}></Button>
this.props.callback('传递给父组件数据')
|
2.3 context
概述:提供了一个无需为没曾组建手动添加 props ,就能在组件树中进行数据传递的方法。
使用:
- 新建
context
文件
1 2 3 4 5
| import React from 'react' const MyContext = React.createContext([])
export default MyContext
|
- 新建
Provider
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| import React, { Component } from 'react' import MyContext from './MyContext'
export default class Provider extends Component { state = { msg: 'hello', list: [], } changeMsg = (msg) => { this.setState({ msg, }) } addList = (todo) => { this.setState({ list: [...this.state.list, todo], }) } render() { const value = { msg: this.state.msg, list: this.state.list, changeMsg: this.changeMsg, addList: this.addList, } return ( <MyContext.Provider value={value}> {this.props.children} </MyContext.Provider> ) } }
|
- class 组件使用 value
1 2 3 4 5 6 7
| <MyContext.Consumer> { (value) => { return <p>{value.msg}</p> } } </MyContext.Consumer>
|
- 函数组件使用
概述: 允许在函数组件中使用 context 对象,在 context 值发生改变时重新渲染。
问题:context 的值发生改变,无论子组件是否引用 value, 都会导致 子组件重新渲染。
1 2 3 4 5 6 7 8 9
| import {useContext} from 'react'
function App() { const context = useContext(MyContext);
return ( <div>{context.msg}</div> ) }
|
2.4 useReducer
概述: useReducer 是 useState 的替代方案,能够解决 useState 状态更新逻辑散落在 UI 中,不能独立复用,不方便测试。
使用:
- 新建
FnProvider
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React, { useState, useReducer } from 'react' import MyContext from './MyContext' import todoReducer from './todoReducer'
export default function FnProvider(props) { const [msg, changeMsg] = useState('') const [todoList, dispatch] = useReducer(todoReducer, []) return ( <div> <h2>FnProvider</h2> <MyContext.Provider value={{ msg, changeMsg, todoList, dispatch, }} > {props.children} </MyContext.Provider> </div> ) }
|
- 新建
reducer.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| export default function todoReducer(state, action) { switch (action.type) { case 'ADD': return [...state, action.todo] case 'DEL': return state.filter((item) => item.id !== action.id) case 'EDIT': const index = state.findIndex((item) => item.id === action.payload.id) const newTodoList = [...state] newTodoList.splice(index, 1, action.payload.todo) return newTodoList default: return state } }
|
- 调用
1 2 3 4 5 6 7
| import FnProvider from './store/FnProvider'
<FnProvider> <HashRouter> <App /> </HashRouter> </FnProvider>
|
1 2 3 4 5 6 7 8 9 10
| import React, { useContext, useState } from 'react'
const context = useContext(MyContext)
onClick={() => context.dispatch({ type: 'DEL', id: item.id, }) }
|
2.5 redux
概述:redux 是 JS 的状态容器,可以使用一个叫 action 对象的事件来管理和更新应用的状态。不依赖于任何的框架,可以在 JS 中独立使用。
2.5.1 环境搭建
新项目:
npx create-react-app 项目名 --template redux
npx create-react-app 项目名 --template redux-typescript
老项目:
npm i redux react-redux
2.5.2 项目引入
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux' import store from './store/index' import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> ); reportWebVitals();
|
2.5.3 store 创建
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createStore, combineReducers } from 'redux' import reducer from './reducer/reducer' import todoReduce from './reducer/todoReducer'
const mainReducer = combineReducers({ reducer: reducer, todo: todoReduce, }) const store = createStore(mainReducer)
store.subscribe(() => { console.log('数据发生改变', store.getState()) })
export default store
|
2.5.4 reducer
概述:是一个纯函数,会根据不同的 action 对象返回一个新的 state
纯函数:
- 不修改传入的参数
- 不执行有副作用的操作,比如API,计时器等等
- 不调用非纯函数,如
new Date()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| const initData = [ { id: Date.now(), flag: false, text: '练习篮球一年半', }, ]
const addActionType = 'ADD' export const addTodo = (payload) => { return { type: addActionType, payload, } }
const editActionType = 'EDIT' export const editTodo = (payload) => { return { type: editActionType, payload, } }
const DelType = 'DEL' export const actionDel = (payload) => ({ type: DelType, payload, })
export default function todoReduce(state = initData, action) { const { type, payload } = action switch (type) { case addActionType: return [...state, payload] case DelType: return state.filter((item) => item.id !== payload.id) case editActionType: const index = state.findIndex((item) => item.id === payload.id) const newTodoList = [...state] newTodoList.splice(index, 1, payload) return newTodoList default: return state } }
|
2.5.5 combineReducers
概述:用于将多个 reducer 合并为一个 reducer。
1 2 3 4 5 6 7 8
| import {combineReducers} from 'redux'
const mainReducer = combineReducers({ home, user, todo }) store = createStore(mainReducer)
|
2.5.6 useSelector
概述:用于组件中获取 state 的hook
1 2 3 4 5 6
| import {useSelector} from 'react-redux
const App =() => { const todoList = useSelector((state) => state.todo) }
|
2.5.7 useDispatch
概述:返回 dispatch 函数的引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import {useDispatch} from 'react-redux
const App =() => { const dispatch = useDispatch()
const onSubmitTodo = () => { dispatch( addTodo({ text, id: Date.now(), flag: false, }) ) setText('') } }
|
2.5.8 redux-thunk
概述: 因为在 reducer 中只能同步操作,那么要想异步处理 redux 就只能在组件中先进行异步操作,然后成功后再 dispatch。但是会让redux 的逻辑散落在UI中,不易于后期管理。
接用该插件可以实现 dispatch 一个函数,然后在这个函数中处理异步。
index.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import { createStore, applyMiddleware, compose } from 'redux' import thunkMiddleware from 'redux-thunk' import logger from 'redux-logger' import mainReducer from './reducer'
const middleWares = [ thunkMiddleware, ]
if (process.env.NODE_ENV === 'development') { middleWares.push(logger) }
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const store = createStore( mainReducer, composeEnhancers(applyMiddleware(...middleWares)) )
store.subscribe(() => { console.log('数据发生改变', store.getState()) })
export default store
|
userAction.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { LOGIN } from '../actionTypes/userActionType' import { loginApi } from '../../http/Api/user'
export const loginAction = (payload) => ({ type: LOGIN, payload, })
export const loginActionAsync = (payload) => (dispatch) => new Promise((resolve, reject) => { loginApi(payload).then((res) => { if (res.data.code == '200') { dispatch(loginAction(res.data.data)) resolve() } else { console.log('登陆失败') reject() } }) })
|
userReducer.js
文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| import { LOGIN, LOGOUT } from '../actionTypes/userActionType'
const INIT_STATE = { userId: null, username: null, authList: [], token: null, }
export default function userReducer(state = INIT_STATE, action) { switch (action.type) { case LOGIN: return { ...state, ...action.payload, }
case LOGOUT: return { userId: null, username: null, authList: [], token: null, } default: return state } }
|
reducer/index.js
文件
1 2 3 4 5 6
| import { combineReducers } from 'redux' import userReducer from './userReducer'
export default combineReducers({ user: userReducer, })
|
使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { loginAction, loginActionAsync } from '../../store/actions/userAction' import { useDispatch } from 'react-redux'
export default function Login() { const navigate = useNavigate() const dispatch = useDispatch() const onFinish = async (value) => { console.log(value) dispatch(loginActionAsync(value)).then(() => { navigate('/') }) return () }
|
2.6 PubSubJs
1 2 3 4 5 6 7 8 9 10 11 12 13
| import PubSub from 'pubsub-js'
PubSub.subscribe("getSate",(_,data)=>{ console.log(data) }) PubSub.subscribe("订阅的消息名称",回调函数,第一个参数是消息名称,可以使用_来占位,第二个是传递的数据 })
PubSub.publish("getSate",{isFrist:false,isLoad:true}) PubSub.publish("订阅的消息名称",传递的数据)
|
2.7 RTK
3. class 生命周期
过时的生命周期:
名称 | 说明 |
---|
componentWillMount | 卸载组件之后 |
componentWillReceiveProps | 父组件进行了更新,子组件先执行这个【注意,第一次传递数据的时候,并不执行】 |
componentWillUpdate | 组件将要更新之前 |
原因: 这些生命周期方法经常被误解和滥用;此外,我们预计,在异步渲染中,它们潜在的误用问题可能更大。
现存生命周期:
名称 | 说明 |
---|
constructor | 加载的时候调用一次,初始化state |
render | 更新 DOM 树的,每次渲染都会重新调用 |
componentDidMount | 组件渲染之后调用一次,一般在这个阶段发起请求、操作 DOM 节点 |
shouldComponentUpdate | 组件接收到新的 props 或者 state 的时候调用,该函数内返回 true 则渲染组件,返回 False 就不渲染 |
componentDidUpdate | 组件更新后,可以获取最新的 state |
componentWillUnmount | 组件卸载,需要清除计时器、事件绑定 等 |
4. 插槽
4.1 匿名插槽
概述:写在每个组件之间的内容都可以被 props.children 获取。
1 2 3 4 5 6 7 8 9 10
| <TodoButton>新增</TodoButton>
... return ( <button> {this.props.children} </button> )
|
4.2 具名插槽
1 2 3 4 5
| <TodoList right={<TodoButton>删除</TodoButton>}></TodoList>
<div>{this.props.right}</div>
|
4.3 作用域插槽
概述:允许给子组件传递自定义内容,同时父组件也能拿到子组件的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <TodoList data={todoList} right={(item) => <TodoButton onClick={() => this.delTodo(item.id)}>删除</TodoButton>}> </TodoList>
const lis = data.map(item => { return ( <div className='todo-item' key={item.id}> <div className='todo-item-text'>{item.text}</div> <div>{this.props.right && this.props.right(item)}</div> </div> ) })
|
5. hooks
概述:可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
注意:
- 只能在函数顶层调用hooks
- 不能写在循环、条件判断中
- 只能是在函数组件中或者自定义hook函数中
5.1 useState
概述:该方法用于定义函数组件的 state 的。
语法:
1 2 3 4 5 6
| import {useState} from 'react';
function App() { const [state, setState] = useState(0); const [msg, setMsg] = useState(''); }
|
5.2 useEffect
概述:该函数可以看做是函数组件的生命周期,包含 componentDidMount
,componentDidUpdate
,componentWillUnmount
,在render 之后执行。
语法:
1 2 3 4 5 6 7 8
| import {useEffect} from 'react'
function App() { useEffect(callback, [dependencies]) }
useLayoutEffect会在渲染的内容更新到DOM上之前进行,会阻塞DOM的更新 如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放在useLayoutEffect
|
dependencies
:该参数是 useEffect
的依赖,可以决定 useEffect
的callback
什么时候执行。
如果该参数不存在,则初始化、组件任意状态值改变都会导致 callback 执行
如果该参数传递一个 [],只有组件初始化和销毁的时候才会触发 callback
该参数传入 [a]。表示在依赖 a 发生变化时才会触发 callback
useEffect(() => {
console.log('Counter');
}, [count]);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| `callback`:回调函数,一般用于发起请求、添加计时器、事件绑定,在该函数中可以return 一个函数,这个函数作为下一次 callback 执行前的调用,用于清除上一次 callback 产生的副作用。
```js useEffect(() => { console.log('Counter'); // 组件加载的时候执行
return () => { // 组件卸载的时候执行 console.log('useEffect 回调函数的返回值函数'); } }, []);
// 没有设置依赖 useEffect(() => { console.log('Counter'); const timer = setInterval(() => console.log(Date.now()), 1000)
return () => { console.log('useEffect 回调函数的返回值函数');
// 组件卸载之后需要干掉计时器 clearInterval(timer) } });
|
5.3 useRef
概述:与createRef
类似,用于获取 dom 元素或者组件实例
语法:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import {useRef} from 'react';
function App() { const myRef = useRef();
useEffect(() => { console.log(myRef.current); }, [])
return ( <div ref={myRef}></div> ) }
|
5.n 自定义hooks
1. 封装useTableList
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
|
import { useState } from 'react'
export function useList(listReq, option) { const [list, setList] = useState([])
const flag = option?.flag || true const cur = option?.cur || 1 const size = option?.size || 5 const data = option?.data || {} const getList = () => { const payload = { ...data, current: cur, pageSize: size } listReq(payload) .then((res) => { console.log(res) if (res.code == '200') { console.log(res.data) } else { console.log('获取数据错误') } }) .catch((err) => { console.log(err) }) } flag && getList()
return { list, getList, } }
|
2. 封装useLocalStorage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| import { useState } from 'react'
export function useSessionStorage(key) { const [state, setState] = useState(() => getStorage())
function getStorage() { const value = sessionStorage.getItem(key) if (value) { try { return JSON.parse(value) } catch (error) { console.log(error) } } else { return undefined } }
const updateState = (newState) => { if (typeof newState == 'undefined') { sessionStorage.removeItem(key) setState(undefined) } else { sessionStorage.setItem(key, JSON.stringify(newState)) setState(newState) } }
return { state, updateState } }
|
6. 重要属性
6.1 ref
概述:用于获取DOM节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class ... { constructor() { super() this.myRef = React.createRef(); }
componentDidMount() { this.myRef.current.focus(); }
render() { return ( <input ref={this.myRef} /> ) } }
|
6.2 fragment
默认情况下 react
的render 只支持单标签渲染,如果想要渲染多标签,那么可以使用该组件,可以再不多增加标签嵌套的情况下,渲染节点。
1 2 3 4 5 6 7 8 9 10 11 12
| <React.Fragment> <div></div> <div></div> <div></div> </React.Fragment>
<> <div></div> <div></div> <div></div> </>
|
6.3 portal
概述:期望渲染的节点能够脱离父组件范围,可以将节点放在任意我期望的位置。
语法:
1 2 3 4
| ReactDOM.craetePortal(child, container)
* child:任何有效的节点或者 React 组件; * container:希望将 child 放在什么位置,应该是一个 DOM 节点;
|
6.4 createElement
概述:因为 JSX 语法中的元素底层就是调用该方法生成组件的。
1 2 3 4 5
| const myDiv = React.createElement('div', null, '标签之间的哦内容');
render() { return <myDiv /> }
|
7. 高阶组件(HOC)
概述:以组件为参数,返回新组件的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const WithTable = (Component) => {
const NewComponent = ({ loading, ...otherProps }) => { console.log('HOC 的 props:', otherProps); if (loading) { return <div>loading...</div> } else { return <Component {...otherProps} /> } }
return NewComponent; } export default WithTable
|
8. 性能优化
父子组件嵌套:
1 2 3 4 5 6
| Father render - ClassA.jsx:14 ClassA constructor ClassA.jsx:23 ClassA render ClassA.jsx:17 ClassA componentDidMount Father useEffect -
|
子组件数据改变后:
父组件状态改变:
- 父组件 render
- 子组件无论与父组件的状态是否有关,都会render
8.1 class 组件优化
8.1.1 shouldComponentUpdate
概述:针对 class 组件,如果 props 和 state 都没有改变
1 2 3 4 5 6 7 8 9 10
| shouldComponentUpdate(nextProps, nextState) { console.log('ClassA shouldComponentUpdate', nextProps, nextState)
if (this.props.num2 != nextProps.num2 || this.state != nextState) { return true; } else { return false; } }
|
8.1.2 PureComponent
概述:React.PureComponent 和 React.Component用法差不多,唯一不同的是 PureComponent 会浅比较 props 和 state 是否相同,从而决定是否重新渲染组件。
8.2 函数组件优化
8.2.1 useMemo
概述:这个函数的返回值会被缓存,在组建第一次渲染的时候执行,以后会在以来发生改变后在此次执行。缓存数据
语法:useMemo(create.deps)
- create:是一个函数,函数的返回值作为缓存值
- deps:依赖,是一个数组
作用:一般用于比较复杂的运算或者耗时操作
8.2.2 useCallback
概述:useCallback 和 useMemo 接受参数一样,都是依赖发生改变后执行回调,否则返回缓存值,区别是 useCallback 是返回函数, useMemo 是返回运算结果。缓存函数
8.3 memo 优化
概述:和 PureComponent 类似,用于性能调优,函数组件和类组件都可以使用,区别是 memo 只能判断 props 而不能处理 state 的改变。
语法:memo(组件)
9. 路由
npm install react-router-dom
9.1 Route
概述:用于匹配当前 url 要渲染的组件
1
| <Route path="url" element={<组件 />} />
|
index:表示默认路由,设置该属性后,Route 不能有子组件
9.2 Routes
概述:作为 Route 的上一级组件,可以优化路径匹配
9.3 Link
用于连接跳转,最终会被渲染为a 标签
to
:链接的路径
{pathname:'/admin',query:'111',state:'111'}
9.4 路由嵌套
1 2 3 4 5 6 7 8 9 10
| <Route path="/" element={<Layout />}> <Route index element={<Home />} /> <Route path="about" element={<About />} /> </Route>
在父组件中使用 <Outlet /> 渲染子组件 <div> <Outlet /> </div>
|
9.5 useNavigate
概述:用于返回一个类似 history 的对象,可用于编程式导航。
1 2 3 4 5 6 7 8 9 10 11 12
| import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate('/home');
navigate(-1)
navigate('/', {replace: true})
|
9.6 useSearchParams
概述:获取 url 的查询参数
1 2 3 4 5 6
| import { useSearchParams } from "react-router-dom";
const [searchParams, setSearchParams] = useSearchParams() console.log('获取查询参数:', searchParams.get('id'));
|
9.7 useParams
概述:获取动态参数path='/admin/:id'
1 2 3 4 5 6
| import { useParams } from "react-router-dom";
const params = useParams(); console.log('获取动态参数:', params);
|
9.8 路由模式
1. hasn模式
hash
模式是一种把前端路由的路径用井号 #
拼接在真实 url
后面的模式。当井号 #
后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发 onhashchange
事件。不会被包括在 http
请求中。- hash变化会触发网页跳转,即浏览器的前进和后退。
hash
通过 window.onhashchange
的方式,来监听 hash
的改变,借此实现无刷新跳转的功能hash
可以改变 url
,但是不会触发页面重新加载,不会出现在``http`中- 首先,
hash
本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,``hash的传参是基于
url`的,如果要传递复杂的数据,会有体积的限制
2. history模式
- 利用H5的 history中新增的两个API
pushState()
和 replaceState()
和一个事件``onpopstate`监听URL变化 history
每次刷新会重新像后端请求整个网址,也就是重新请求服务器。如果后端没有及时响应,就会报错404!history
模式不仅可以在``url`里放参数,还可以将数据存放在一个特定的对象中。- 修改历史状态: 包括了 HTML5 History Interface 中新增的
pushState()
和 replaceState()
方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了 url,但浏览器不会立即向后端发送请求。如果要做到改变 url 但又不刷新页面的效果,就需要前端用上这两个 API。 - 切换历史状态: 包括
forward()
、back()
、go()
三个方法,对应浏览器的前进,后退,跳转操作。
3. 两种模式对比
- history 模式的 pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
- pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
- pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
- pushState() 可额外设置 title 属性供后续使用。
- hash 模式下,仅 hash 符号之前的 url 会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回 404 错误;history 模式下,前端的 url 必须和实际向后端发起请求的 url一致,如果没有对用的路由处理,将返回 404 错误。
10. 事件处理
- React的事件是通过onXxx属性指定事件处理函数
- React使用的都是自定义的时间,而不是原生的事件
- React中的事件是通过事件委托方式处理的
- 通过event.target得到发生事件的Dom元素对象
- 事件中必须返回的是函数
10.1 受控和非受控组件
受控: 对于受控组件来说,输入的值始终由 React 的 state 驱动。
非受控: 表单元素的值不会更新state。输入数据都是现用现取的。
10.2 class 事件处理
绑定事件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| export default class Counter extends Component { constructor(props) {
this.changeCount = this.changeCount.bind(this); } changeCount() {}
render() { return <div onClick={this.changeCount}></div> } }
export default class Counter extends Component { changeCount() {}
render() { return <div onClick={this.changeCount.bind(this)}></div> } }
export default class Counter extends Component { changeCount() {}
render() { return <div onClick={() => this.changeCount()}></div> } }
export default class Counter extends Component { changeCount = () => {}
render() { return <div onClick={this.changeCount}></div> } }
|
参数传递:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| export default class Counter extends Component { changeCount(str) {}
render() { return <div onClick={this.changeCount.bind(this, 'abc')}></div> } }
export default class Counter extends Component { changeCount(str) {}
render() { return <div onClick={() => this.changeCount('abc')}></div> } }
<button data-msg="hi" onClick={this.changeCount}></button>
changeCount = (e) => {
}
|
10.3 函数组件事件处理
1 2 3 4 5 6 7 8 9 10 11
| function App(props) { function onHandler() { console.log('事件'); }
return ( <div> <button onClick={onHandler}>按钮</button> </div> ) }
|
11. Diff算法
11.1 index
问题:
若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 界面效果没问题,但效率低。
如果结构中还包含输入类的DOM:会产生错误DOM更新 界面有问题。
注意! 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
11.2 diff
Diff算法其实就是react生成的新虚拟DOM和以前的旧虚拟DOM的比较规则:
- 如果旧的虚拟DOM中找到了与新虚拟DOM相同的key:
- 如果内容没有变化,就直接只用之前旧的真实DOM
- 如果内容发生了变化,就生成新的真实DOM
- 如果旧的虚拟DOM中没有找到了与新虚拟DOM相同的key:
二、React 周边
1. css处理
1.1 局部 css 样式
要求新建:文件名.module.css
- 在该文家中创建css代码
导入样式:
1 2 3
| import styles from '文件名.module.css';
<button className={styles.btn_default}></button>
|
1.2 引入sass
概述:只需要在当前环境中安装以下模块就可以直接使用。
npm i sass
2. antd 引入
npm i antd
配置模块安装:
npm i @craco/craco -D --legacy-peer-deps
npm i babel-plugin-import -D
修改package.json 里的 scripts
1 2 3
| "script": { "start": "craco start" }
|
根目录下创建配置文件 craco.config.js 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| module.exports = { babel: { plugins: [ ["import", { libraryName: 'antd', libraryDirectory: 'es', style: 'css' }] ] }, webpack: { alias: { '@': path.resolve(__dirname, 'src') } } };
|