# study-react
**Repository Path**: monoseven/study-react
## Basic Information
- **Project Name**: study-react
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2022-01-26
- **Last Updated**: 2024-06-21
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
# 一、基础部分
## 1. 虚拟DOM
### ① 使用JSX创建虚拟DOM
`const VDOM =
Hello,React{content}
`
### ② 使用JS创建虚拟DOM
`const VDOM = React.createElement('h1',{id:'title'},'你好,React')`
### ③ 说明
1. 虚拟 DOM 本质上一个 object 对象。
2. 虚拟 DOM 比较轻,相对于真实 DOM 来说,属性少得多。
3. 虚拟 DOM 最终会被 React 转换为真实 DOM 。
## 2. 渲染虚拟DOM到页面
`ReactDOM.render(VDOM, document.getElementById('app'))`
## 3. JSX语法规则
1. 定义虚拟DOM的时候,不要写引号;
2. 标签中混入JS表达式时,要使用 {};
3. 样式的类名指定不要用 class ,要使用 className (避免与 ES6 class 类混乱);
4. 内联样式要用 `style={{ color: 'blue' }}` 的形式写;
5. 只能有一个根标签;
6. 标签必须闭合;
7. 标签首字母;
* 如果小写字母开头,则将该标签转为html中同名元素,如果没有改标签对应的同名元素,则报错;
* 如果大写字母开头,react就会渲染对应的组件,如果组件没有定义,那么就会报错。
## 4. 函数式组件
1. 函数式组件中 this 是 Undefined ,因为babel编译之后,使用的是严格模式
2. React解析组件标签,找到了 MyComponent 组件,发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转换成真实DOM,随后展现在页面中
## 5. 类式组件
1. 定义方法 `class MyComponent extends React.Component`
2. React 解析组件标签,找到了 MyComponent 组件,发现组件是使用类定义的,随后 new 出来该类型的实例并通过该实例调用原型上的 render 方法
3. 在类式组件中,返回的虚拟 dom 传入的方法调用者不是实例,因此,类式组件中的方法在虚拟DOM中使用的时候,要使用 bind 绑定 this ,或者使用箭头函数
## 6. 受控组件和非受控组件
1. 非受控组件是指 页面中输入类 DOM 中的数据现用现取,一般会使用到ref
2. 受控组件是指 输入类 DOM 中的数据变化时,将其新的值保存到 state 中,使用时不用再获取
## 7. Fragment 文档碎片组件
1. 使用 Fragment 标签 ,jsx不会报错,且渲染后,不会多这一层标签,可以接收一个key属性
2. 也可以使用空标签,但是空标签不能添加属性
## 8. Context 上下文组件
1. 使用React.createContext()创建一个上下文对象,这个上下文对象需要大家都能访问到;
> `const MyContext = React.createContext()`
2. 使用该对象的Provider属性作为标签,包裹需要使用该属性的标签,该标签的value属性存储共享的数据
> ` ` B中所有子组件均可以使用
3. 类式组件:在需要使用该对象的组件中,声明static contextType = MyContext,即可在this.context中使用;
> `static contextType = MyContext`
> `this.context` 即可访问
4. 函数式组件: 使用 MyContext.Consumer 标签包裹,里边写一个回调函数,参数为需要共享的数据。
> ` {value => {return value}} `
## 9. PureComponent
1. Component 的2个问题
* 只要执行 setState() ,即使不改变状态,组件也会重新 render
* 只要当前组件重新 render() ,就会导致子组件都重新 render ,效率低
2. 解决
* 重写 `shouldComponentUpdate` 方法,只有 state 和 props 数据有变化时才返回 true
* 使用 `PureComponent` 代替继承的 `Component`
* 这个组件重写了shouldComponentUpdate,只有state和props数据有变化时才返回true
* 注意:这个里边的比较只是浅比较,如果对象内部数据变了,那么不会更新(不能使用原对象),因此不要直接修改state,而是要产生新数据(不能push等)
## 10. ErrorBoundary 错误边界
1. 用途:用来捕获后代组件的错误,渲染出来备用页面
2. 使用方式:
* getDerivedStateFromError 当此组件的子组件出现报错的时候,会触发这个钩子函数,并携带错误信息。返回的是一个 DerivedState ,会替换掉原来的state
```javascript
state = {
hasError: ''
}
static getDerivedStateFromError(error) {
console.log(error);
return {
hasError: error
}
}
```
* 根据错误状态选择渲染的组件
> `{this.state.hasError ? 当前网络不稳定,稍后再试
: }`
* 使用 componentDidCatch 当此组件的子组件出现报错的时候,会触发这个钩子函数,用于统计错误信息,可进行发送给服务器等操作
```javascript
componentDidCatch(){
console.log('组件渲染时出错');
}
```
# 二、组件的三大属性
## 1. state 属性
### ① 基本使用
1. 定义:在类式组件中,直接定义 `state:{}` 属性
2. 使用:通过 `this.state.属性` 即可在组件中使用其中的值
3. 设置:`this.setState({ isHot: !this.state.isHot })` 通过 setState 方法即可修改 state 中的值并刷新页面。
### ② 对象式 setState
1. `setState(stateChange,[callback])` 例如:`this.setState({ count: count + 1 })`
2. 参数说明:
* stateChange --- 状态改变对象,需要更新哪些状态 (更新是一种合并,而并不是替换)
* callback --- 可选参数,当更新完状态之后,可以在这个回调里边看到更新后的状态(状态更新是异步的)
### ③ 函数式 setState
1. `setState(updaterFun, [callback])` 例如:`this.setState((state, props) => ({ count: state.count + 1 }))`
2. 参数说明:
* updaterFun --- 是一个函数,返回值和stateChange相同;可以接收到state和props这两个参数
* callback --- 可选参数,当更新完状态之后,可以在这个回调里边看到更新后的状态(状态更新是异步的)
### ④ 总结
1. 对象是的setState是函数式的简写方式(语法糖);
2. 如果新状态不依赖原状态,那么使用对象方式;如果新状态依赖原状态,那么使用函数方式;
3. 注意:更新是异步的,更新操作后边不能直接使用。如果要在setState执行后获取最新的状态数据,要在第二个callback中读取;
4. 注意:更新是一种合并,而并不是替换。
## 2. props 属性
### ① 基本使用
1. 传入:在组件标签上可以添加属性,属性值可以是任意类型,包括对象、函数等
2. 使用:组件标签上的属性,在组件中,可以通过 `this.props.属性` 即可在组件中使用组件标签中的值
3. 设置:props数据是单向的,不能在组件中直接修改,可以在父组件中,在组件标签上传入修改该变量的方法,在子组件上调用即可。
### ② 类型限制
1. 安装并引入相关包:`import PropTypes from 'prop-types'`
2. 类型限制:使用静态属性 `static propTypes = { name: PropTypes.string.isRequired }`
* 类型 string、number、func 等;
* 必要性 isRequired。
3. 默认值设置:使用静态属性 `static defaultProps = { name: '李明' }`
### ③ 其他技巧和注意事项
1. 对象的结构:在组件标签上,可以通过 `{...object}` 将该对象中的所有属性添加到该组件中
2. 类式组件中,构造器函数默认传入一个变量就是props,一般用不到
3. 父组件中的标签体内容,会作为 children 属性从 prop 中传入,可以调用,也可以将这个属性和属性值写在标签中,即可显示出来
### ④ renderProps 插槽
1. 如何向组件内部动态传入带内容的结构(标签)
* 在Vue中,使用slot技术,也就是通过组件标签体传入结构
* 在React中,① 可以使用children props组件标签体传入;② 可以使用render props组件标签属性回调函数传入,且可以传出来数据
2. children props 使用方式
* 在父组件中,在子组件标签中嵌套上需要插入的组件
> ` `
* 在子组件中,返回的虚拟 DOM 中需要插入的位置写上
> `{this.props.children}`
2. render props 使用方式:
* 在父组件中,在子组件标签上添加属性,属性值为函数,函数返回值是一个组件
> ` } />`
* 在子组件中,返回的虚拟 DOM 中需要插入的位置写上
> `{this.props.render(this.state.test)}`
* 以上方法将子组件中的 `this.state.test` 返回给父组件,供父组件使用(类似VUE中的slotdata)
## 3. refs 属性
### ① string方式(可能会被弃用)
1. 定义:在标签上,添加 ref 属性; 例如:``
2. 使用:在组件中,使用 `this.refs.属性获取`; 例如:`this.refs.userName`
3. 注意:这种方式在将来的版本中可能会被弃用,因其可能会造成性能问题
### ② 回调函数方式
1. 定义:在标签上,添加 ref 属性;回调函数传入的就是这个DOM ` this.userNameNote = cNote} type="text" />`
2. 使用:在组件中,使用 `this.变量` 即可使用这个DOM
3. 注意:
* ref 的回调函数执行的时候,会将当前的真实DOM传入回调函数,我们可以在这时,将DOM存储在实例中
* 如果写的是行内回调函数,那么在更新dom的时候,不能确定还是之前的回调,因此会执行两次 第一次传入null 用来清空之前变量中的DOM;
* 如果使用了类中的方法的引用地址,那么就不会出现上方这种情况
### ③ createRef方式
1. 定义:创建变量 `inputNote = React.createRef()`,组件标签上将此变量绑定到ref属性上 ``
2. 使用:在组件中,使用 `this.inputNote.current` 使用真实DOM
3. 注意:`React.createRef` 调用后,会返回一个容器,该容器可以存储被ref标识的节点,该容器是专人专用的。
### ④ forwardRef方法
1. 定义:可以自定义父组件获取ref的真实DOM
2. 使用:
* 子组件使用 forwardRef 函数包裹
```javascript
export default forwardRef((props,ref) => {
return (
<>
aaaa
bbbb
>
)
})
```
* 父组件中使用ref的时候,得到的就是 h2 的真实 DOM
# 三、生命周期
## 1. 旧生命周期
1. `componentWillMount` 组件即将挂载
2. `componentDidMount` 组件挂载完成
3. `shouldComponentUpdate` 组件是否可以更新,可以控制要不要更新(更新的阀门)
4. `componentWillUpdate` 组件即将更新
5. `componentDidUpdate` 组件更新完成
6. `componentWillUnmount` 组件即将卸载
7. `componentWillReceiveProps` 组件将要接收新的props
## 2. 新生命周期
### ① 即将移除掉的(实际上也是很少使用的)
1. `UNSAFE_componentWillMount` 组件即将挂载
2. `UNSAFE_componentWillUpdate` 组件即将更新
3. `UNSAFE_componentWillReceiveProps` 组件将要接收新的props
### ② 新生命周期
1. `static getDerivedStateFromProps` 初次渲染时、父组件更新传进来的值时都会执行
* 接收两个参数,props 和 state,
* 必修有返回值,返回 null 或者 派生state
* 一般用于: state 永远只由 props 决定,即:将props作为返回值
2. `componentDidMount` 组件挂载完成
3. `shouldComponentUpdate` 组件更新前的钩子,可以控制要不要更新(更新的阀门)
4. `getSnapshotBeforeUpdate` 可以在更新之前运行,可以拿到快照
* 拿到一些想要保存的数据、信息等,通过返回值返回,这个返回值会被下一个生命周期接收到,用于进行更新后的操作
5. `componentDidUpdate` 组件更新完成
* 接收三个参数 (preProps, preState, prePayload) , prePayload 是快照钩子函数传递过来的
6. `componentWillUnmount` 组件即将卸载
# 四、路由(4.0-5.0)
## 1. 基本使用
1. 使用 `BrowserRouter`, `HashRouter` 标签包裹所有组件的父组件,方便使用
2. 明确好界面中的导航区和展示区
2. 导航区:使用 `Link` `、NavLink` 标签配置路由路径,to属性为跳转的目标 `首页`
* Link 与 NavLink 的区别:NAVLink中,活跃的路由自带一个 active 属性
* 使用 `replace={true}` 属性可以使用 replace 模式
3. 展示区:使用 `Route` 标签配置展示组件,`component` 属性位展示的组件 ``
* Route 需要使用 `Switch` 组件包裹 (唯一匹配,避免匹配到多个路由)原因:第三点
* Route 列表的最后可以使用 `Redirect` 组件进行重定向,`to="/home"`。如果前边所有路由都不匹配,就会重定向
* Route 的匹配是模糊匹配,如果 Route 的 path 和 Link 的 to 前边有匹配,就会进入路由 例子:`path="/" to="/home"`
## 2. 唯一匹配、严格匹配与模糊匹配
1. 唯一匹配:在展示区,使用 `Switch` 包裹所有的 `Route` 组件。包裹之后,就能够唯一匹配,提高效率
2. 严格匹配和模糊匹配
* 默认为模糊匹配,即:输入的路径要包含匹配的路径,且顺序要一致
* 开启严格模式,要在 `Route` 组件上,增加 `exact={true}` 属性,这样就必须一致才能匹配,缺点:可能导致无法匹配到嵌套的路由
## 3. 路由组件和普通组件
### ① 写法不同
1. 普通组件直接书写,路由组件需要先使用 Route 组件,component 属性为目标组件
### ② 接收的props不同
1. 普通组件中的 props 就是父组件中,组件标签上声明的属性
2. 路由组件中的 props 会在普通组件的基础上,增加三个属性
* > `history`: go(),goBack(),goForward(),push(),replace()
* > `location`: pathname,searth,state
* > `match`: params,path,url
### ③ 将普通组件转换成路由组件
1. 因在普通组件中,无法使用上述三个属性,因此可以使用 withRouter 方法将普通组件转换成路由组件
* 示例:export default withRouter(组件名) 代替 export default 组件
## 4. 编程式路由
1. 实际上就是通过路由组件props中的 history 属性中的方法进行路由的跳转,无需使用Link标签
> `this.props.history.push('/home/news/detail')`
> `this.props.history.replace('/home/news/detail', {})`
> `this.props.history.goForward()`
## 5. 路由传参
### ① param 传参
1. 向路由组件传递Param参数
> `{item.title} `
2. 声明接收params参数
> ``
3. 在目标组件中的 `props.match.params` 中可以找到这些参数
> `const {id,title} = this.props.match.params`
### ② search 传参
1. 向路由组件传递query参数
> `{item.title}`
2. 无需声明接收,正常注册即可
> ``
3. 在目标组件中的 `props.location.search` 中可以找到这些参数, 但是没有整理
> 需要使用 react脚手架带的 querystring 包中的 .stringfy() .parse() 整理
### ③ state 传参
1. 向路由组件传递query参数
> `{item.title}`
2. 无需声明接收,正常注册即可
> ``
3. 在目标组件中的 `props.location.state` 中可以找到这些参数
> `const {id,title} = this.props.location.state`
### ④ 三种传参方式的区别
1. 前两个会暴露在URL中,后一个不会
2. 三个都不会随着刷新而消失,前两个保存在URL中,后一个保存在浏览器的history对象中。但是清空浏览器缓存后,第三种会消失
## 6. 懒加载
1. 引入组件的时候,使用react的lazy方法:
> `const Home = lazy(() => import('./Home'))`
2. 路由需要被Suspense包裹,fallback属性中放置一个懒加载时的过渡组件
> `加载中}> `
# 五、路由(6.0)
## 1. 基本使用
### ① 路由组件的嵌套
1. 使用方式
```html
} >
{/* 当访问到上边的路由时,由于这个路由没有path,自动进入index */}
} />
} />
} />
```
2. 注意:嵌套的路由,父路由对应的组件中,需要一个 `` 组件进行渲染,这样,会将子路由对应的组件渲染到 Outlet 的位置(相当于route-view)
3. 重定向:Route 上的 index 属性,当这个层级的路由没有路径的时候,就默认进入这个组件
## 2. 编程式路由
1. 使用 useNavigate Hook函数:`const navigate = useNavigate(); navigate('/');`
## 3. 路由传参
### ① param 传参
1. 向路由组件传递Param参数
> `用户1`
2. 声明接收params参数
> `} />`
3. 在目标组件中,使用 useParams Hook函数
> `const params = useParams(); const id = params.id;`
### ② search 传参和 state 传参
1. 目标组件中,使用 useLocation() 使用
## 4. 终极使用方法 useRoutes
1. 定义路由数组:
```javascript
const routeList = [
{
path: '/',
element: ,
meta: {
title: '首页'
},
children: [{
index: true,
element: lazyLoad(),
meta: {
title: '首页',
icon: AppstoreOutlined
}
},{
path: '/user',
element: lazyLoad(),
meta: {
title: '用户',
icon: MailOutlined
}
}]
}, {
path: '/login',
element: ,
meta: {
title: '登录'
},
}, {
path: '*',
element: ,
meta: {
title: '404'
},
}
]
```
2. 使用 useRoutes Hook 函数:该函数返回的是路由组件的虚拟 dom ,可作为页面 view 的总入口
```javascript
export default function RouteList() {
return useRoutes(routeList)
}
```
## 5. 懒加载
1. 使用 react 包中的 lazy 方法: `import { lazy, Suspense } from 'react'`
2. 引入组件的时候,使用: `const Home = lazy(() => import('../pages/Home'))`
3. 定义方法: (这个方法可以避免闪屏,同时保证了路由组件被 Suspense 包裹)
```javascript
const lazyLoad = (children) => (
loading...>}>
{children}
)
```
4. 路由中,element 的属性值为: `element: lazyLoad()`
# 六、PubSub 事件总线
## 1. 用途:使用事件总线,可以在任意两组件之间传递参数
## 2. 使用
1. 下载并引入包 pubsub-js
> `import PubSub from 'pubsub-js'`
2. 发布事件:
```javascript
PubSub.publish('setNewItem',{
id: nanoid(),
name: target.value,
done: false
})
```
3. 订阅事件
```javascript
componentDidMount() {
// 回调函数中,第一个参数是事件名称,第二个参数是携带的参数
PubSub.subscribe('setNewItem', (_, payload) => {
console.log('获取到新的消息', payload);
})
}
```
# 七、Redux 状态管理
## 1. 基本构成
### ① action
1. 作用:动作的对象
2. 包含两个属性:
* type:标识属性,值为字符串
* data:数据属性,值类型任意,可选
3. 例子:{type:"ADD",data:{name:'tom',age:18}}
4. 文件的使用:
```javascript
export const creareIncrementAction = data => ({ type: INCREMENT, data })
//这样,在 dispatch的使用 可用
store.dispatch(creareIncrementAction(value))
//代替
store.dispatch({ type: 'increment', data: value })
```
### ② reducer
1. 用于初始化状态、加工状态;
2. 该文件是用于创建一个为count组件服务的reducer,reducer的本质就是一个函数
3. 加工时,根据旧的state和action,产生新的state的纯函数
4. reducer函数会接收到两个参数,分别为:之前的状态(preState)、动作对象(action)
5. 示例:
```javascript
export default function countReducer(preState = 0, action) {
console.log(preState, action);
// 从action对象中获取type、data
const { type, data } = action
switch (type) {
case 'increment':
return parseInt(preState) + parseInt(data)
case 'decrement':
return parseInt(preState) - data
default:
return preState
}
}
```
### ③ store
1. 将state、action、reducer联系在一起的对象(整个应用只有一个 Store 对象)
2. 如何得到此对象
```javascript
import {createStore} from 'redux'
import reducer from './reducers'
const store = createStore(reducer)
```
3. 此对象的功能
* getState:得到state
> `const count = store.getState()`
* dispatch(action):分发action,触发reducer调用,产生新的state
> `store.dispatch({ type: 'increment', data: value })`
* subscribe(listener):注册监听,当产生了新的state时,自动调用;然后render重新渲染页面 (这个监测可以写到 index 中,实现监控所有组件中的redux变化)
> `store.subscribe((params) => { this.setState({}) })`
## 2. 异步 Action
1. 值是一般对象,就是同步action,如果值是一个函数,就是异步action
2. 异步操作不想交给组件自身,想交给action的时候使用(例如很多组件都需要调用同一个接口)
3. 何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回才能操作的时候
4. 具体使用方式:
* 安装 redux-thunk 中间件,并配置在store中
```javascript
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import countReducer from './count_reducer'
export default createStore(countReducer, applyMiddleware(thunk))
```
* 创建 action 的函数不再返回一个对象,而是一个函数,该函数中写异步任务
```javascript
export const creareIncrementAsyncAction = (data, time) => {
return () => {
setTimeout(() => {
store.dispatch(creareIncrementAction(data))
}, time)
}
}
```
* 异步任务有了结果之后,分发一个同步的 action 去真正操作数据
> `setTimeout(() => { store.dispatch(creareIncrementAction(data)) }, time)`
* 调用异步ACTION
> `store.dispatch(creareIncrementAsyncAction(value,500))`
5. 备注:异步任务action不是必须要写的,完全可以自己等待异步任务的结果再去分发同步action
## 3. 多个状态管理
1. 使用 combineReducers 方法
```javascript
import {createStore, combineReducers} from 'redux'
import reducer1 from './reducers1'
import reducer2 from './reducers2'
const allReducer = combineReducers({
reducer1: reducer1,
reducer1: reducer2
})
const store = createStore(allReducer)
```
# 八、React-Redux 状态管理
## 1. 基本概念
1. 所有使用状态管理的UI组件,都应该被一个容器组件包裹,他们是父子关系
2. 容器组件是真正和 redux 打交道的,里边可以随意的使用 Redux 的 API;但是 UI 组件不能够使用 Redux 的 API
3. 容器组件 通过props 将 Redux中的状态和操作状态的方法 传递给UI组件
## 2. 使用方法
1. 创建容器组件:使用 react-redux 包的 connect 方法
```javascript
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 引入CountUI组件
import CountUI from "../../page/Count";
// 使用connect()()创建并暴露一个Count的容器组件。content第一次调用的时候,需要传入两个参数(函数)
export default connect(mapStateToProps, mapDispatchToProps)(CountUI)
```
2. 创建状态:`mapStateToProps` 和修改状态的方法 `mapDispatchToProps` 传入connect
```javascript
import { creareIncrementAction, creareDecrementAction, creareIncrementAsyncAction } from '../../redux/count_action'
// mapStateToProps 函数的返回值作为 状态 传递给了UI组件
function mapStateToProps(state, { store }) {
return {
count: state
}
}
// mapDispatchToProps 函数的返回值作为 操作状态的方法 传递给了UI组件
function mapDispatchToProps(dispatch, { store }) {
return {
jia: data => dispatch(creareIncrementAction(data)),
jian: data => dispatch(creareDecrementAction(data)),
jiaAsync: (data, time) => dispatch(creareIncrementAsyncAction(data, time)),
}
}
```
3. 子组件使用状态
* 子组件使用状态: this.prop.count
* 子组件修改状态:this.prop.jian()
4. 使用容器组件
```jsx
import Count from '@/containers/Count'
import store from '@/redux/store'
```
## 3. 优化简写
1. 容器组件 和 UI组件 可以整合成一个文件
2. 无需自己给容器组件传入store,给 App 组件包裹一个 `` 即可
3. 无需再手动添加代码监听 store 中状态的变化,react-redux 自动监听
4. mapDispatchToProps 返回值如果是一个对象,可以直接用对象代替
## 4. 终极使用方式
1. 定义好 UI 组件,无需暴露
* > `class Person extends Component ()`
2. 使用 react-redux 包的 connect 方法,传入UI组件和 状态及修改状态的方法
* > `import { connect } from 'react-redux';`
* > `export default connect(state => ({person: state.ren,count:state.he}),{addPerson: createAddPersonAction})(Person)`
* > `export default connect({ren,he} => ({ren,he}),{createAddPersonAction})(Person)` 简写
3. UI 组件中使用 `this.props.属性` 读取和操作状态
4. 使用 Provider 包裹 App
* > `import { Provider } from 'react-redux'`
* > ``
## 5. React-Redux 开发者工具
1. 商店下载插件
2. 下载并在store中引入包:`import { composeWithDevTools } from 'redux-devtools-extension'`
3. 创建store的时候:`export default createStore(allReducer, composeWithDevTools(applyMiddleware(thunk)))`
# 九、Hook
## 1. useState()
1. API介绍
存储 react state 状态变量
2. 使用方法
`const [state, setState] = useState({name:'tom', age:10 })`
3. 参数介绍
* state -- state的值
* setState -- 设置state的方法,setState调用的时候,可以传入一个对象或者函数(对象是函数的简写)
* useState -- 可以传入任意类型的数据,如果传入的是方法,那么值是方法的返回值,而不是方法(用于初始值需要计算才能确定的情况)
* 与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果。
## 2. useEffect()
1. API介绍
* 给函数组件增加了操作副作用的能力(DOM操作、数据请求、组件更新)
* 跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途
* 可以写多个
* 可以实现 watch 监听的作用
2. 使用方法
```javascript
useEffect(() => {
console.log('相当与 componentDidMount 和 componentDidUpdate');
return ()=>{
console.log('相当于 componentWillUnmount');
}
}, [])
```
3. 参数介绍
1. 回调函数:函数中的内容会在组件挂载完成后执行,函数的返回值会在组件销毁前执行
2. 数组:
* 如果不写这个参数,回调函数中的内容会在组件挂载、更新的时候执行
* 如果写的是空数组,回调函数中的内容不会在更新时执行,只会在挂载时执行
* 如果写的是某个状态,回调函数中的内容会在挂载、此状态变化时执行
3. 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
## 3. useRef()
1. API介绍
* 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数
* 返回的 ref 对象在组件的整个生命周期内持续存在
* 一般用于获取 DOM 元素,同时也可以用于保存一个变量(因为在函数式组件中定义的普通变量会在更新时被重新初始化)
2. 使用方法
* 获取 DOM 元素
```javascript
const myRef = React.useRef(null)
myRef.current.value
```
* 保存变量
```javascript
const myRef = React.useRef({name:'tom'}) // 保存的值不会随着页面刷新被初始化
myRef.current.name = 'jerry'
console.log(myRef.current.name)
```
3. 参数介绍
1. 保存 DOM 元素,绑定到 ref 属性上的时候,返回值会初始化为 myRef 的 current 上
2. 保存变量时,保存的值在 `myRef.current` 里边
3. 通过 `myRef.current` 获取绑定的 DOM、变量
4. 当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
## 4. useContext()
1. API介绍
* 对应 1.8 中介绍的 Context 上下文组件,用于父子、祖孙传值
* 该API的作用是:在函数式组件中提供了 更便捷获取上下文组件提供的变量的方法
2. 使用方法
* 使用React.createContext()创建一个上下文对象,这个上下文对象需要大家都能访问到;
> `const MyContext = React.createContext()`
* 使用该对象的Provider属性作为标签,包裹需要使用该属性的标签,该标签的value属性存储共享的数据
> ` ` B中所有子组件均可以使用
* 类式组件:在需要使用该对象的组件中,声明static contextType = MyContext,即可在this.context中使用;
> `static contextType = MyContext`
> `this.context` 即可访问
* 函数式组件: 使用 MyContext.Consumer 标签包裹,里边写一个回调函数,参数为需要共享的数据。
* > `1. {value => {return value}} `
* > `2. const value = useContext(MyContext)`
3. 参数介绍
* value就是 provide提供的值
## 5. useMemo()
1. API介绍
* 解决在渲染过程中避免重复渲染的问题
* 类似于类式组件中的 shouldComponentUpdate 的作用
* 个人理解:不直接使用 state 或者 props 的值,而是监控这些值的变化,得到一个新的值,使用新的值在DOM上。根据这个函数执行时机,可以控制组件的更新(感觉这有点VUE中计算属性的意思,但是比计算属性更灵活一点)
2. 使用方法
```javascript
const [count, setCount] = useState(0)
const res = useMemo(() => {
return count
}, [])
DOM 中使用 {res}
```
3. 参数介绍
1. 回调函数:函数中的内容会在 组件渲染的过程中执行(是在useEffect前执行)
2. 数组:
* 如果不写这个参数,回调函数中的内容 `一定会执行`,即监控所有状态
* 如果写的是空数组,回调函数中的内容 `一定不执行`,那么res得到的值不会变化,所以组件不会更新
* 如果写的是某个状态,回调函数中的内容 `会在此状态变化时执行`
## 5. useCallback()
1. API介绍
* 和 useMemo 类似,但是返回的是一个函数,使用时要加括号调用
2. 使用方法
```javascript
const [count, setCount] = useState(0)
const callback = useCallback(() => {
return count
}, [])
DOM 中使用 {callback()}
```
3. 参数介绍
1. 回调函数:函数中的内容会在 组件渲染的过程中执行(是在useEffect前执行)
2. 数组:
* 如果不写这个参数,回调函数中的内容 `一定会执行`,即监控所有状态
* 如果写的是空数组,回调函数中的内容 `一定不执行`,那么res得到的值不会变化,所以组件不会更新
* 如果写的是某个状态,回调函数中的内容 `会在此状态变化时执行`
## 6. useImperativeHandle()
1. API介绍
* 使用 ref 时自定义暴露给父组件的实例值
* 避免使用 ref 这样的命令式代码。
* useImperativeHandle 应当与 forwardRef 一起使用
2. 使用方法
* 子组件使用 forwardRef 函数包裹
```javascript
const Imperative = forwardRef((props,refa) => {
const inputRef = useRef()
const [count, setCount] = useState(0)
const [num, setNum] = useState(0)
useImperativeHandle(refa,()=>({
name:"张三",
focus: ()=>{
inputRef.current.focus()
},
count
}),[num])
return (
<>
{count}
{num}
>
)
})
```
* 父组件中使用ref的时候,得到的就是 h2 的真实 DOM
```javascript
export default () => {
const el = useRef() // 这里获取到的ref,就是 {name:"张三", focus:fun}
return (
)
}
```
3. 参数介绍
1. 回调函数:函数中的内容会在 父组件获取ref的时候,将对象中的内容返给父组件
2. 数组:
* 如果不写这个参数,回调函数中 返回的东西是最新的
* 如果写的是空数组,回调函数中的内容 返回的东西都不是最新的
* 如果写的是某个状态,回调函数中的内容 会在状态变化后,将最新的东西返回
## 7. useLayoutEffect()
1. API介绍
* 和 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect
* 跟 class 组件中的 componentDidMount 之前调用,会造成阻塞 ( 暂时理解成:componentWillMount、componentWillUpdate )
* 销毁的时候,也是这个先执行
2. 使用方法
```javascript
useLayoutEffect(() => {
console.log('相当与:componentWillMount 和 componentWillUpdate');
return ()=>{
console.log('相当于 componentWillUnmount');
}
}, [])
```
3. 参数介绍
1. 回调函数:函数中的内容会在组件挂载完成后执行,函数的返回值会在组件销毁前执行
2. 数组:
* 如果不写这个参数,回调函数中的内容会在组件挂载、更新的时候执行
* 如果写的是空数组,回调函数中的内容不会在更新时执行,只会在挂载时执行
* 如果写的是某个状态,回调函数中的内容会在挂载、此状态变化时执行
3. 如果你要使用此优化方式,请确保数组中包含了所有外部作用域中会发生变化且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。
## 8. useReducer()
1. API介绍
* `const [state, dispatch] = useReducer(reducer, initialArg, init);`
* useState 的替代方案。
* 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
* (如果你熟悉 Redux 的话,就已经知道它如何工作了。)
2. 使用方法
```javascript
const [state, dispatch] = useReducer((preState, action)=>{
switch(action.type){
case "setName":
return {
...preState,
name:action.value
}
case "setAge":
return {
...preState,
age:action.value
}
default:
return preState
}
}, { name:'张三', age:18 });
// 使用状态
const {name} = state
// 设置状态
dispatch({
type:"setName",
value:"李四"
})
```
3. 参数介绍
* reducer:和 Redux 中 reducer 的写法一样
* initialArg:初始值的设置
* init:初始化初始值的方法,上一个参数要设置成 undefined
* 结合 useContext 可以实现 Redux 数据共享,思路如下:
* 使用 useReducer 创建出来 state 和 dispatch
* 使用 createContext 在公共位置创建出 MyContext
* 使用 MyContext 的 MyContext.Provider 包裹需要共享数据的组件,value 设置成 {state, dispatch}
* 在内部的组件中 使用 useContext 结构出 state, dispatch ,即可使用 state 获取状态,dispatch 设置状态
## 9. 自定义Hook
1. 要求
* 和普通的函数本质上没有区别,都是做一些函数的封装,方便使用
* 必须以use开头
* 其中可以使用 React 等原生 Hook 来封装
2. 使用方法
* 创建Hook函数
```javascript
const useCus = (val,num)=>{
const [count, setCount] = useState(val),
const add = ()=>{
setCount(count + num)
}
return {count, add}
}
```
* 使用Hook函数
```javascript
const {count, add} = useCus(10,2)
count // 当前的值
add() // 使当前的值加2
```
# 其他
## 1. 事件处理特性
1. 通过onXxx属性指定时间处理函数(注意大小写) `onclick onClick`
* React使用的是自定义(合成)事件,不是原生的DOM事件 -- 为了更好地兼容性
* React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) --为了更高效
2. 最好通过 `event.target` 得到发生时间的DOM元素对象 减少使用ref
## 2. DIFF算法
1. key的作用:当状态中的数据发生变化,组件重新渲染时, react 会根据新数据生成新的虚拟DOM,然后进行虚拟 DOM 比较
2. 比较规则:
* 旧的虚拟DOM中找到了与新虚拟DOM相同的key,如果没变,则使用之前的真实 DOM ,如果变了,生成真实 DOM 并替换
* 如果没有找到相同的key,那么会生成真实 DOM 渲染到页面中
3. 使用index作为key的弊端
* 例如 list 在前边插入,会导致所有 index 产生变化,导致不必要的重新渲染
* 如果结构中 有输入类的 DOM ,会导致显示错位
## 3. 高阶函数
1. 如果符合下面两个规范中的任何一个,就是高阶函数
* 如果一个函数,接收的参数是一个函数,那么称之为高阶函数
* 如果一个函数调用的返回值仍然是一个函数,那么称之为高阶函数
## 4. 函数的柯里化
1. 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
## 5. 样式的模块化
1. 修改文件名为 `index.module.css`
2. 使用变量接收,自动转换成对象 `import styleSheet from './index.module.css'`
3. 类名使用 `变量.类名` `className={styleSheet.title}`
## 6. 配置proxy代理
1. 在src下创建文件 `setupProxy.js`
2. 下载并在此文件中引入包 `http-proxy-middleware`
3. 配置
```javascript
module.exports = function (app) {
app.use('/api', // 指定需要转发的请求
createProxyMiddleware({
target: 'http://localhost:18080',
changeOrigin: true, // 控制服务器收到的请求头中HOST的值,如果没有这个,服务器其实也是可以知道有没有跨域 可以显示真正的端口
pathRewrite(path) { // 重写请求路径
return path.replace('/api', '');
}
})
);
}
```
## 7. fetch请求
1. 基本用法
```javascript
fetch('/api/getList').then((res) => {
console.log('服务器连接成功', res);
return res.json()
}).then((res) => {
console.log('数据获取成功', res);
}).catch(err => {
console.log('失败', err);
})
```
2. 简化用法
```javascript
try {
const response = await fetch('/api/getList')
const res = await response.json()
console.log(res);
} catch (err) {
console.log('失败', err);
}
```
## 8. 纯函数
1. 只要是同样的输入,必定得到同样的输出。内部一定没有Date、随机数等方法
2. 必须遵守以下约束
* 不能改写原来的参数数据
* 不产生任何副作用,例如网络请求、输入、输出设备
* 不能 `Date.now()` 或者 `Math.random()` 等不纯的方法
* redux 的 reducer 必须是一个纯函数