进阶react.js

组件生命周期

组件的生命周期有助于理解组件的运行方式,完成更复杂的组件功能、分析组件错误原因等
组件的生命周期: 组件从被创建到挂载到页面中运行,再到组件不在时卸载的过程
生命周期的每个阶段总是伴随着一些方法调用,这些方法就是生命周期的钩子函数
构造函数的作用:为开发人员在不同阶段操作组件提供了实际


image.png
创建时(挂载阶段)
image.png
class App extends React.Component {
  constructor() {
    super()
    console.log(1)
  }
  componentDidMount() {
    console.log(3)
  }
  render() {
    console.log(2)
    return (
      
啊哈
) } }
image.png
更新时

执行时机:setState()、 forceUpdate()、 组件接收到新的props
说明:以上三者任意一种变化,组件就会重新渲染

image.png

卸载时

执行时机:组件从页面中消失
作用:用来做清理操作


image.png

新版完整生命钩子函数

image.png

getDerivedStateFromProps()

  • getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容
  • 不管原因是什么,都会在每次渲染前触发此方法

shouldComponentUpdate()

  • 根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染
  • 当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true

getSnapshotBeforeUpdate()

  • getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期的任何返回值将作为参数传递给 componentDidUpdate()
  • 此用法并不常见,但它可能出现在 UI 处理中,如需要以特殊方式处理滚动位置的聊天线程等

render-props模式

  • 思考:如果两个组件中的部分功能相似或相同,该如何处理?
  • 处理方式:复用相似的功能
  • 复用什么?
    • state
    • 操作state的方法
  • 两种方式:
    • render props模式
    • 高阶组件(HOC)
  • 注意: 这两种方式不是新的API,而是利用React自身特点的编码技巧,演化而成的固定模式
// 子组件
class Hello extends React.Component {
  state = {
    name: '肖'
  }
  render() {
    return this.props.render(this.state);
  }
}
//父组件
class App extends React.Component {
  render() {
    return (
      
父组件 { return

{msg.name}

}} />
) } }
children代替render属性
  • 注意:并不是该模式叫 render props就必须使用名为render的prop,实际上可以使用任意名称的prop
  • 把prop是一个函数并且告诉组件要渲染什么内容的技术叫做: render props模式
  • 推荐:使用childre代替render属性
// 子组件
class Hello extends React.Component {
  state = {
    name: '肖'
  }
  render() {
    return this.props.children(this.state);
  }
}
//父组件
class App extends React.Component {
  render() {
    return (
      
父组件 {({ name }) =>

{name}

}
) } }

高阶组件

  • 高阶组件(HOC、Higher-Order Component) 是一个函数,接收要包装的组件,返回增强后的组件

  • 高阶组件内部创建了一个类组件,在这个类组件中提供复用的状态逻辑代码,通过prop将复用的状态传递给被包装组件WrappedComponent

  • 创建一个函数,名称约定以with开头

  • 指定函数参数,参数应该以大写字母开头

  • 在函数内部创建一个类组件,提供复用的状态逻辑代码,并返回

  • 在该组件中,渲染参数组件,同时将状态通过prop传递给参数组件

  • 调用该高阶组件,传入要增强的组件,通过返回值拿到增强后的组件,并将其渲染到页面

function WithMouse(WrappedComponent) {
  class Mouse extends React.Component {
    state = {
      time: new Date().getTime()
    }
    render() {
      return 
    }
  }
  return Mouse
}
// 需要加强的组件
const Position = (props) => {
  console.log(props);
  return 

{props.time}

} // 传递封装加强 let MousePosition = WithMouse(Position); //父组件 class App extends React.Component { render() { return (
) } }
设置displayName
  • 使用高阶组件存在的问题:得到两个组件的名称相同
  • 原因:默认情况下,React使用组件名称作为displayName
  • 解决方式:为高阶组件设置displayName,便于调试时区分不同的组件
  • displayName的作用:用于设置调试信息(React Developer Tools信息)
    image.png
传递props
  • 问题:如果没有传递props,会导致props丢失问题
  • 解决方式: 渲染WrappedComponent时,将state和props一起传递给组件
    image.png

React原理

setState()说明
  • setState()更新数据是异步的
  • 注意:使用该语法,后面的setState不要依赖前面setState的值
  • 多次调用setState,只会触发一次render
推荐语法
  • 推荐:使用 setState((state,props) => {}) 语法
  • 参数state: 表示最新的state
  • 参数props: 表示最新的props
  add = () => {
    this.setState(({ count }) => {
      return {
        count: count + 1
      }
    })
    console.log(this.state.count); //js是单线程 先执行同步再执行异步
  }
第二个参数
  • 场景:在状态更新(页面完成重新渲染)后立即执行某个操作
  • 语法:setState(update[,callback])
  add = () => {
    this.setState(({ count }) => {
      return {
        count: count + 1
      }
    }, () => {
      console.log('这个回调函数会在状态更新后立即执行')
    })
    console.log(this.state.count); //js是单线程 先执行同步再执行异步
  }

JSX语法的转化过程

  • JSX仅仅是createElement() 方法的语法糖(简化语法)
  • JSX语法被 @babel/preset-react 插件编译为createElement() 方法
  • React 元素: 是一个对象,用来描述你希望在屏幕上看到的内容


    image.png

组件更新机制

  • setState() 的两个作用

    • 修改state
    • 更新组件
  • 过程:父组件重新渲染时,也会重新渲染子组件,但只会渲染当前组件子树(当前组件以其所有子组件)

image.png

组件性能优化

减轻state
  • 减轻state:只存储跟组件渲染相关的数据(比如:count/ 列表数据 /loading等)
  • 注意:不用做渲染的数据不要放在state中
  • 对于这种需要在多个方法中用到的数据,应该放到this中
class Hello extends React.Component {
  // 初始化
  componentDidMount() {
    this.timeID = setInterval(() => {
      console.log(1);
    }, 2000)
  }
  // 卸载 移除计时器
  componentWillUnmount() {
    clearInterval(this.timeID);
  }
  render() {
    return 
子组件
} }

避免不必要的重新渲染

  • 组件更新机制:父组件更新会引起子组件也被更新,这种思路很清晰
  • 问题:子组件没有任何变化时也会重新渲染
  • 如何避免不必要的重新渲染?
  • 解决方式:使用钩子函数 shouldComponentUpdate(nextProps, nextState)
    • 在这个函数中,nextProps和nextState是最新的状态以及属性
  • 作用:这个函数有返回值,如果返回true,代表需要重新渲染,如果返回false,代表不需要重新渲染
  • 触发时机:更新阶段的钩子函数,组件重新渲染前执行(shouldComponentUpdate => render)
class App extends React.Component {
  state = {
    count: 0
  }
  // 每次点击生成一个随机数
  add = () => {
    this.setState({
      count: Math.floor(Math.random() * 3)
    })
  }

  // 将要更新ui的时候会执行这个钩子函数
  shouldComponentUpdate(nextProps, nextState) {
    // 判断一下当前生成的 值是否与页面的值相等
    if (nextState.count !== this.state.count) {
      console.log('已改变');
      return true
    }
    return false
  }
  render() {
    return (
      
随机数:{this.state.count}
) } }

利用props参数来判断是否需要进行更新

class App extends React.Component {
    state = {
        number: 0
    }
    // 点击事件,每次点击生成一个随机数
    hanldeBtn = () => {
        this.setState({
            number: Math.floor(Math.random() * 3)
        })
    }

    render() {
        return (
            
) } } class NumberBox extends React.Component { // 将要更新UI的时候会执行这个钩子函数 shouldComponentUpdate(nextProps, nextState) { // 判断一下当前生成的 值是否与页面的值相等 if (nextProps.number !== this.props.number) { return true } return false } render() { return (

随机数:{this.props.number}

) } }

纯组件

  • 纯组件: PureComponent 与 React.Component 功能相似
  • 区别: PureComponent 内部自动实现了 shouldComponentUpdate钩子,不需要手动比较
  • 原理:纯组件内部通过分别比对前后两次 props和state的值,来决定是否重新渲染组件
class App extends React.PureComponent{
  render(){
    return (
      
纯组件
) } }
实现原理
  • 说明:纯组件内部的对比是 shallow compare(浅层对比)
  • 对于值类型来说:比较两个值是否相同
  • 引用类型:只比对对象的引用地址是否相同
const obj = { name: '肖' };
const newObj = obj;
newObj.name = '哈';
console.log(newObj == obj); //ture
  • 注意:state 或 props 中属性值为引用类型时,应该创建新数据,不要直接修改原数据

虚拟DOM和Diff算法

本质上就是一个JS对象,用来描述你希望在屏幕上看到的内容


image.png
Diff算法
  • 初次渲染时,React会根据初始化的state(model),创建一个虚拟DOM对象(树)
  • 根据虚拟DOM生成真正的DOM,渲染到页面
  • 当数据变化后(setState()),会重新根据新的数据,创建新的虚拟DOM对象(树)
  • 与上一次得到的虚拟DOM对象,使用Diff算法比对(找不同),得到需要更新的内容
  • 最终,React只将变化的内容更新(patch)到DOM中,重新渲染到页面


    image.png

React路由

现代的前端应用大多数是SPA(单页应用程序),也就是只有一个HTML页面的应用程序。因为它的用户体验更好、对服务器压力更小,所以更受欢迎。为了有效的使用单个页面来管理多页面的功能,前端路由应运而生。

  • 前端路由功能:让用户从一个视图(页面)导航到另一个视图(页面)
  • 前端路由是一套映射规则,在React中,是URL路径与组件的对应关系
  • 使用React路由简单来说,就是配置路径和组件
安装
npm install --save react-router-dom
导入
import {BrowserRouter as Router, Route, Link} from 'react-router-dom'
// 导入
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'

// 定义内容
const First = () => {
  return 

页面一的内容

} //使用Router组件包裹整个应用 const App = () => { return (
{/* 指定路由入口 */} 页面一 {/* 指定路由出口 */}
) }
常用组件说明
  • Router组件:包裹整个应用,一个React应用只需要使用一次
    • 两种常用的Router: HashRouter和BrowserRouter
    • HashRouter: 使用URL的哈希值实现 (localhost:3000/#/first)
    • 推荐 BrowserRouter:使用H5的history API实现(localhost3000/first)
  • Link组件:用于指定导航链接(a标签)
    • 最终Link会编译成a标签,而to属性会被编译成 a标签的href属性
  • Route组件:指定路由展示组件相关信息
    • path属性:路由规则,这里需要跟Link组件里面to属性的值一致
    • component属性:展示的组件
    • Route写在哪,渲染出来的组件就在哪

路由的执行过程

  • 当我们点击Link组件的时候,修改了浏览器地址栏中的url
  • React路由监听地址栏url的变化
  • React路由内部遍历所有的Route组件,拿着Route里面path规则与pathname进行匹配


    image.png
  • 当路由规则(path)能够匹配地址栏中的pathname时,就展示该Route组件的内容
编程式导航
  • 场景:点击登陆按钮,登陆成功后,通过代码跳转到后台首页,如何实现?
  • 编程式导航:通过JS代码来实现页面跳转
  • history是React路由提供的,用于获取浏览器历史记录的相关信息
  • push(path):跳转到某个页面,参数path表示要跳转的路径
  • go(n):前进或后退功能,参数n表示前进或后退页面数量
  this.props.history.push('路由')
默认路由
  • 现在的路由都是通过点击导航菜单后展示的,如果进入页面的时候就主动触发路由呢
  • 默认路由:表示进入页面时就会匹配的路由
  • 默认路由:只需要把path设置为 '/'

匹配模式

模糊匹配模式
  • 当Link组件的to属性值为 '/login' 时候,为什么默认路由也被匹配成功?
  • 默认情况下,React路由是模糊匹配模式
  • 模糊匹配规则:只要pathname以path开头就会匹配成功
// 定义内容
const First = () => {
  return 

页面一的内容

} //使用Router组件包裹整个应用 const App = () => { return (
{/* 指定路由入口 */} 页面一 {/* 指定路由出口 */} {/* 模糊匹配 匹配成功*/}
) }
  • path 代表Route组件的path属性
  • pathname 代表Link组件的to属性(也就是location.pathname
    image.png
精准匹配
  • 默认路由认可情况下都会展示,如果避免这种问题?
  • 给Route组件添加exact属性,让其变为精准匹配模式
  • 精确匹配:只有当path和pathname完全匹配时才会展示改路由
        {/* 此时,该组件只能匹配pathname="/" 这种情况 */}
        

你可能感兴趣的:(进阶react.js)