1. setState

setState更新状态的2种写法

  1. (1). setState(stateChange, [callback])------对象式的setState
  2. 1.stateChange为状态改变对象(该对象可以体现出状态的更改)
  3. 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用
  4. (2). setState(updater, [callback])------函数式的setState
  5. 1.updater为返回stateChange对象的函数。
  6. 2.updater可以接收到stateprops
  7. 3.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
  8. 总结:
  9. 1.对象式的setState是函数式的setState的简写方式(语法糖).
  10. 2.使用原则:
  11. (1).如果新状态不依赖于原状态 ===> 使用对象方式
  12. (2).如果新状态依赖于原状态 ===> 使用函数方式
  13. (3).如果需要在setState()执行后获取最新的状态数据, 要在第二个callback函数中读取.
  14. 生命周期
  15. componentWillMount
  16. componentWillReceiveProps
  17. componentWillUpdate
  18. 现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

2. lazyLoad

路由组件的lazyLoad

  1. // 1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
  2. const Login = React.lazy(()=>import('@/pages/Login'))
  3. // 2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
  4. <Suspense fallback={<h1>loading.....</h1>}>
  5. <Switch>
  6. <Route path="/xxx" component={Xxxx}/>
  7. <Redirect to="/login"/>
  8. </Switch>
  9. </Suspense>
  10. // 3.当前页面刷新清除路由携带的state数据
  11. this.props.history.replaceState(null,'路由地址')

集中式路由配置方式

由于react中的路由编写简单,Route会存在于很多个不同的组件当中,维护难度增加.所以很多时候我们还是需要路由config这种模式来集中式管理我们的路由.

在src根目录下创建一个router文件夹,创建route.js,定义路由配置如下:

  1. import Discover from "../pages/Discover"
  2. import Playlist from "../pages/Discover/Playlist"
  3. import Toplist from "../pages/Discover/Toplist"
  4. import Djradio from "../pages/Discover/Djradio"
  5. import Kdp from "../pages/Discover/Djradio/kdp"
  6. import Lk from "../pages/Discover/Djradio/lk"
  7. import Mine from "../pages/Mine"
  8. const routes = [
  9. {
  10. path: "/discover",
  11. component: Discover,
  12. children: [
  13. {
  14. path: "/discover/playlist",
  15. component: Playlist
  16. },
  17. {
  18. path: "/discover/toplist",
  19. component: Toplist
  20. },
  21. {
  22. path: "/discover/djradio",
  23. component: Djradio,
  24. children: [
  25. {
  26. path: "/discover/djradio/kdp",
  27. component: Kdp
  28. },
  29. {
  30. path: "/discover/djradio/lk",
  31. component: Lk
  32. }
  33. ]
  34. }
  35. ]
  36. },
  37. {
  38. path: "/mine",
  39. component: Mine,
  40. }
  41. ]
  42. export default routes

在router/创建compireRouter.js 用于编译Route

  1. //这个是个高阶组件 专门负责处理路由逻辑
  2. import React, { Component } from "react"
  3. import { Route, Redirect } from "react-router-dom"
  4. class CompileRouter extends Component {
  5. constructor() {
  6. super()
  7. this.state = {
  8. C: null //用于存储通过routes编译好的Route组件的集合
  9. }
  10. }
  11. async renderContent() {//这个方法用于通过配置生成路由
  12. let { routes } = this.props;
  13. console.log(routes)
  14. let C = routes.map(async route => {
  15. //处理路由重定向
  16. let { redirect } = route;
  17. if (redirect) {
  18. return <Redirect key={route.path} to={redirect} from={route.path} />
  19. }
  20. //判断是否是异步组件
  21. try {
  22. let component = await route.component().then(value => value.default)
  23. route.component = component
  24. } catch (e) { }
  25. //Route中的render属性 和children属性差不多 Render属性可以用于渲染嵌套组件
  26. /**
  27. * @param {Object} props 路由的三大对象 location history match
  28. */
  29. return (
  30. <Route path={route.path} exact={route.exact} key={route.path} render={(props) => {
  31. return <route.component {...props} routes={route.children} />
  32. }} />
  33. )
  34. })
  35. //由于 C得到的数组是Pomiese 所以需要一次执行 等待结果
  36. C = await Promise.all(C)
  37. //将map得到的值 设置给state
  38. this.setState({
  39. C
  40. })
  41. }
  42. componentDidMount() {
  43. this.renderContent()
  44. }
  45. render() {
  46. /**
  47. * @param {Array} C Route的数组的集合
  48. */
  49. let { C } = this.state;
  50. return (
  51. C
  52. )
  53. }
  54. }
  55. export default CompileRouter
  1. React-router-dom里面的render属性讲解:
  2. 可以渲染子组件 children不同 render需要路径匹配才会渲染子组件。

3. Hooks

1. React Hook/Hooks是什么?

  1. (1). HookReact 16.8.0版本增加的新特性/新语法
  2. (2). 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

  1. (1). State Hook: React.useState()
  2. (2). Effect Hook: React.useEffect()
  3. (3). Ref Hook: React.useRef()

3. State Hook

  1. (1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
  2. (2). 语法: const [xxx, setXxx] = React.useState(initValue)
  3. (3). useState()说明:
  4. 参数: 第一次初始化指定的值在内部作缓存
  5. 返回值: 包含2个元素的数组, 1个为内部当前状态值, 2个为更新状态值的函数
  6. // 改变对象的写法 const [obj, setObj] = useState({name: '李雷'})
  7. // setObj({ // 一定要产生新的对象 不然视图会不更新 --- 这个容易踩坑
  8. ...obj,
  9. name: '李四'
  10. age: 18 // 给对象添加的新的属性
  11. })
  12. // 改变数组的写法 const [arr, setArr] = useState([1,2,3])
  13. setArr( value => { // 接受一个函数
  14. arr.push(4)
  15. return [ ...arr ]
  16. })
  17. // const [func, setFunc] = useState(() => {
  18. return {name: '韩梅梅'} // useState内部也可以接受一个函数 必须要有一个返回值 func接受函数的返回值
  19. })
  20. (4). setXxx()2种写法:
  21. setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
  22. setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

4. Effect Hook

  1. (1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
  2. (2). React中的副作用操作:
  3. ajax请求数据获取
  4. 设置订阅 / 启动定时器
  5. 手动更改真实DOM
  6. 组件更新
  7. (3). 语法和说明:
  8. useEffect(() => {
  9. // 在此可以执行任何带副作用操作
  10. return () => { // 在组件卸载前执行
  11. // 在此做一些收尾工作, 比如清除定时器/取消订阅等
  12. }
  13. }, [stateValue])
  14. // 如果指定的是[], 回调函数只会在第一次render()后执行, 当里面有stateValue值的时候,当此值发生改变的时候会监听.
  15. // 第二个参数不写的时候 会监听所有的状态 -- 只要有状态更新的时候,就会触发useEffect的调用.
  16. (4). 可以把 useEffect Hook 看做如下三个函数的组合
  17. componentDidMount()
  18. componentDidUpdate()
  19. componentWillUnmount()

5. Ref Hook

  1. (1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
  2. (2). 语法: const refContainer = useRef()
  3. (3). 作用:保存标签对象,功能与React.createRef()一样 拿到dom元素 或者dom元素的值 存取变量
  4. // 用法: import { useRef } from 'react'
  5. const inputRef = useRef(null) // 可以单独对 input button 这样的按钮进行绑定
  6. ---------------------------------------------------------------------
  7. 如果是想获取子组件的实列对象的话 应该怎样来进行绑定尼?
  8. forwardRef useImperativeHandle(ref, createHandle, [deps]) 搭配使用
  9. function FancyInput(props, ref) {
  10. const inputRef = useRef()
  11. useImperativeHandle(ref, () => ({
  12. focus: () => { // 此方法就是子组件向父组件向外暴露的方法. 此方法可以直接return 得到子组件定义的变量值.
  13. inputRef.current.focus()
  14. }
  15. }))
  16. return <input ref={inputRef} ... />
  17. }
  18. FancyInput = forwardRef(FancyInput)

6. useContext

  1. import { createContext, useContext } from 'react'
  2. const MyContext = createContext()
  3. <MyContext.Provider value={123} >
  4. <Children> <Children/> // Children是子组件
  5. <MyContext.Provider>
  6. // 在子组件里面进行接受
  7. const value = useContext(MyContext)
  8. ----------------------------------------------------------------------
  9. 类组件的写法
  10. 在子孙组件中:
  11. 第一种写法:
  12. <MyContext.ConSumer>
  13. { value => <div>{ value }</div>}
  14. </MyContext.ConSumer>
  15. 第二种写法:
  16. static contextType = MyContext
  17. render() {
  18. const value = this.context // 第二种的写法 比较简洁
  19. return (<div></div>)
  20. }

7. useMemo

  1. 1. useMemo---解决组件渲染的性能问题---用到缓存的技术
  2. shouldComponentUpdate 类似作用, 在渲染的过程中避免重复渲染的问题
  3. 当状态或者父组件传来的属性发生变化时,更新组件
  4. 2. useMemo()是一个函数,有两个参数,第一个参数是个函数,第二个参数是个数组。
  5. useMemo(() => {}, [默认可以不写])
  6. 不写的话回调函数一定会执行默认监控所有的状态 -- 写空数组的话 回调函数不执行 -- 可以监听某个属性的状态值
  7. useMemoEffect执行的时间不同,useEffect是在componentDidMount以后执行的,而useMemo是在组件渲染过程中执行的。
  8. let res = useMemo(() => {
  9. return { count, num }
  10. },[])

8.useCallback

  1. 作用: 避免组件的重复渲染,提高性能
  2. 可以控制组件什么时候更新
  3. 同样用到缓存技术,和useMemo不同的是
  4. useCallback缓存的是个函数, 是个函数就可以执行
  5. useCallback(() => {},[可以不写])
  6. // 当第二个参数是空数组的时候,只会渲染一次 后面都不会渲染
  7. // 当第二个参数不是空数组的时候 只有当依赖项改变的时候 才会进行渲染
  8. const callback = useCallback(() => {},[可以不写])
  9. callback是个函数 可以直接callback() 执行
  10. useMemo的区别: 前者固定是一个函数, 后者固定的是一个值.

9.useReducer

  1. import React, { useContext, useReducer } from 'react';
  2. const reducer = (preState: any, action: any) => {
  3. const newState = { ...preState };
  4. switch (action.type) {
  5. case 'change-a':
  6. newState.a = newState.a + action.value;
  7. return newState;
  8. case 'change-b':
  9. newState.b = newState.b + action.value;
  10. return newState;
  11. default:
  12. return preState;
  13. }
  14. };
  15. const initailState = {
  16. a: 1,
  17. b: 2
  18. };
  19. const GlobalContext = React.createContext({});
  20. export default function Usereducer() {
  21. const [state, dispatch] = useReducer(reducer, initailState);
  22. return (
  23. <GlobalContext.Provider value={
  24. { state, dispatch }
  25. }>
  26. <div>
  27. <Child1 />
  28. <Child2 />
  29. <Child3 />
  30. </div>
  31. </GlobalContext.Provider>
  32. )
  33. }
  34. function Child1() {
  35. const { dispatch }: any = useContext(GlobalContext);
  36. return <div>
  37. <button onClick={() => { dispatch({ type: 'change-a', value: 2 }) }}>改变a--点击加2</button>
  38. <button onClick={() => { dispatch({ type: 'change-b', value: 4 }) }}>改变b--点击加4</button>
  39. </div>
  40. }
  41. function Child2() {
  42. const { state }: any = useContext(GlobalContext);
  43. return <div>
  44. Child2---{state.a}
  45. </div>
  46. }
  47. function Child3() {
  48. const { state, dispatch }: any = useContext(GlobalContext);
  49. return <div>
  50. Child3---{state.b}
  51. <button onClick={() => { dispatch({ type: 'change-a', value: 2 }) }}>改变a--点击加2</button>
  52. </div>
  53. }

image.png


4. Fragment

使用

  1. <Fragment><Fragment>
  2. <></>

作用

可以不用必须有一个真实的DOM根标签了


5. Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

  1. 1) 创建Context容器对象:
  2. const XxxContext = React.createContext()
  3. 2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
  4. <xxxContext.Provider value={数据}>
  5. 子组件
  6. </xxxContext.Provider>
  7. 3) 后代组件读取数据:
  8. //第一种方式:仅适用于类组件
  9. static contextType = xxxContext // 声明接收context
  10. this.context // 读取context中的value数据
  11. //第二种方式: 函数组件与类组件都可以
  12. <xxxContext.Consumer>
  13. {
  14. value => ( // value就是context中的value数据
  15. 要显示的内容
  16. )
  17. }
  18. </xxxContext.Consumer>

注意

  1. 在应用开发中一般不用context, 一般都用它的封装react插件

6. 组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
  2. 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

  1. 办法1:
  2. 重写shouldComponentUpdate()方法
  3. 比较新旧stateprops数据, 如果有变化才返回true, 如果没有返回false
  4. 办法2:
  5. 使用PureComponent
  6. PureComponent重写了shouldComponentUpdate(), 只有stateprops数据有变化才返回true
  7. 注意:
  8. 只是进行stateprops数据的浅比较, 如果只是数据对象内部数据变了, 返回false
  9. 不要直接修改state数据, 而是要产生新数据
  10. 项目中一般使用PureComponent来优化

7. render props

如何向组件内部动态传入带内容的结构(标签) ?

  1. Vue中:
  2. 使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
  3. React中:
  4. 使用children props: 通过组件标签体传入结构
  5. 使用render props: 通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性

children props

  1. <A>
  2. <B>xxxx</B>
  3. </A>
  4. {this.props.children}
  5. 问题: 如果B组件需要A组件内的数据, ==> 做不到

render props

  1. <A render={(data) => <C data={data}></C>}></A> // 相当于传入了render函数给子组件
  2. A组件: {this.props.render(内部state数据)}
  3. C组件: 读取A组件传入的数据显示 {this.props.data}

8. 错误边界

理解:

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

  1. // 生命周期函数,一旦后台组件报错,就会触发
  2. static getDerivedStateFromError(error) {
  3. console.log(error);
  4. // 在render之前触发
  5. // 返回新的state
  6. return {
  7. hasError: true,
  8. };
  9. }
  10. componentDidCatch(error, info) {
  11. // 统计页面的错误。发送请求发送到后台去
  12. console.log(error, info);
  13. }

9. 组件通信方式总结

组件间的关系:

  • 父子组件
  • 兄弟组件(非嵌套组件)
  • 祖孙组件(跨级组件)

几种通信方式:

  1. 1.props
  2. (1).children props
  3. (2).render props
  4. 2.消息订阅-发布:
  5. pubs-subevent等等
  6. 3.集中式管理:
  7. reduxdva等等
  8. 4.conText:
  9. 生产者-消费者模式

比较好的搭配方式:

  1. 父子组件:props
  2. 兄弟组件:消息订阅-发布、集中式管理
  3. 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)