# 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 必须是一个纯函数