Skip to content

生命周期

https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/

组件的生命周期可分成三个状态:

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM

生命周期的方法有

  • **componentWillMount **在渲染前调用,在客户端也在服务端。
  • **componentDidMount **: 在第一次渲染后调用,只在客户端。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异步操作阻塞UI)。
  • **componentWillReceiveProps **在组件接收到一个新的 prop (更新后)时被调用。这个方法在初始化render时不会被调用。
  • **shouldComponentUpdate **返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。可以在你确认不需要更新组件时使用。
  • componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
  • **componentDidUpdate **在组件完成更新后立即调用。在初始化时不会被调用。
  • componentWillUnmount在组件从 DOM 中移除之前立刻被调用。

hooks

userStateuseEffectuseContext

useContext可以帮助我们跨越组件层级直接传递变量,实现共享。 需要注意的是useContext和redux的作用是不同的!!!

useContext:解决的是组件之间值传递的问题

redux:是应用中统一管理状态的问题

但通过和useReducer的配合使用,可以实现类似Redux的作用。

useReducer

useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)

在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数 。

useCallback

返回一个memoized回调函数,我的理解即返回一个函数的句柄,等同于函数的变量,因此你可以使用memoizedCallback()进行执行该函数或者传递给事件和子组件,这里可以推荐绝大多数事件或者子组件的方法使用useCallback,避免组件更新重复渲染。 因此useCallback中的doSomething并不会在定义时就执行,而是需要手动调用返回的memoizedCallback才是真的执行。简单理解为useCallback定义了一个函数,仅在deps发生变化时重新定义该函数,否则该函数的变量不会变化,事件和子组件内容也就不用重新绑定或者渲染。

useMemo

作为性能优化的手段(缓存数据) 原理与 class 组件的 PureComponent 优化原理相同,对 props 进行了浅比较

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。常见访问子组件

useImperativeMethodsuseMutationEffectuseLayoutEffect

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。

useEffect 和 useLayoutEffect 的区别?

useEffect 在渲染时是异步执行,并且要等到浏览器将所有变化渲染到屏幕后才会被执行。 useLayoutEffect 在渲染时是同步执行,其执行时机与 componentDidMount,componentDidUpdate 一致

对于 useEffect 和 useLayoutEffect 哪一个与 componentDidMount,componentDidUpdate 的是等价的?

useLayoutEffect,因为从源码中调用的位置来看,useLayoutEffect的 create 函数的调用位置、时机都和 componentDidMount,componentDidUpdate 一致,且都是被 React 同步调用,都会阻塞浏览器渲染。

useEffect 和 useLayoutEffect 哪一个与 componentWillUnmount 的是等价的?

同上,useLayoutEffect 的 detroy 函数的调用位置、时机与 componentWillUnmount 一致,且都是同步调用。useEffect 的 detroy 函数从调用时机上来看,更像是 componentDidUnmount (注意React 中并没有这个生命周期函数)。

为什么建议将修改 DOM 的操作里放到 useLayoutEffect 里,而不是 useEffect?

可以看到在流程9/10期间,DOM 已经被修改,但但浏览器渲染线程依旧处于被阻塞阶段,所以还没有发生回流、重绘过程。由于内存中的 DOM 已经被修改,通过 useLayoutEffect 可以拿到最新的 DOM 节点,并且在此时对 DOM 进行样式上的修改,假设修改了元素的 height,这些修改会在步骤 11 和 react 做出的更改一起被一次性渲染到屏幕上,依旧只有一次回流、重绘的代价。 如果放在 useEffect 里,useEffect 的函数会在组件渲染到屏幕之后执行,此时对 DOM 进行修改,会触发浏览器再次进行回流、重绘,增加了性能上的损耗。

函数式编程

编程范式 纯函数 不可变值

JSX 本质 和 vdom

JSX 即 createElement 函数 执行生成 vnode patch(elem, vnode) patch(vnode, newVnode)

合成事件

所有事件挂载到 document 上 event 不是原生的,是 SyntheticEvent 合成事件对象,模拟出来 DOM 事件所有能力 所有的事件,都被挂载到 document 上 和 Vue 事件不同,和 Dom 事件也不同

javascript
import React from 'react'

class EventDemo extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      name: 'zhangsan',
      list: [
        {
          id: 'id-1',
          title: '标题1',
        },
        {
          id: 'id-2',
          title: '标题2',
        },
        {
          id: 'id-3',
          title: '标题3',
        },
      ],
    }

    // 修改方法的 this 指向
    this.clickHandler1 = this.clickHandler1.bind(this)
  }
  render() {
    // this - 使用 bind
    // return <p onClick={this.clickHandler1}>{this.state.name}</p>;

    // this - 使用静态方法
    // return <p onClick={this.clickHandler2}>clickHandler2 {this.state.name}</p>;

    // event
    // return (      ,,,
    // 	<a href="https://imooc.com/" onClick={this.clickHandler3}>
    // 		click me
    // 	</a>
    // );

    // 传递参数 - 用 bind(this, a, b)
    return (
      <ul>
        {this.state.list.map((item, index) => {
          return (
            <li
              key={item.id}
              onClick={this.clickHandler4.bind(this, item.id, item.title)}
            >
              index {index}; title {item.title}
            </li>
          )
        })}
      </ul>
    )
  }
  clickHandler1() {
    // console.log('this....', this) // this 默认是 undefined
    this.setState({
      name: 'lisi',
    })
  }
  // 静态方法,this 指向当前实例
  clickHandler2 = () => {
    this.setState({
      name: 'lisi',
    })
  }
  // 获取 event
  clickHandler3 = (event) => {
    event.preventDefault() // 阻止默认行为
    event.stopPropagation() // 阻止冒泡
    console.log('target', event.target) // 指向当前元素,即当前元素触发
    console.log('current target', event.currentTarget) // 指向当前元素,假象!!!

    // 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
    console.log('event', event) // 不是原生的 Event ,原生的 MouseEvent
    console.log('event.__proto__.constructor', event.__proto__.constructor)

    // 原生 event 如下。其 __proto__.constructor 是 MouseEvent
    console.log('nativeEvent', event.nativeEvent)
    console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发
    console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!

    // 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
    // 2. event.nativeEvent 是原生事件对象
    // 3. 所有的事件,都被挂载到 document 上
    // 4. 和 DOM 事件不一样,和 Vue 事件也不一样
  }
  // 传递参数
  clickHandler4(id, title, event) {
    console.log(id, title)
    console.log('event', event) // 最后追加一个参数,即可接收 event
  }
}

export default EventDemo

为何要合成事件机制

更好的兼容性和跨平台 载到 document ,减少内存消化,避免频繁解绑 方便事件的统一管理(如事务机制)

image.png

setState 和 batchUpdate

有时异步(普通使用),有时同步(setTimeout、DOM事件) 有时合并(对象形式),有时不合并(函数形式) 后者比较好理解(像Object.assign),主要讲解前者

javascript
import React from 'react'

// 函数组件(后面会讲),默认没有 state
class StateDemo extends React.Component {
  constructor(props) {
    super(props)

    // 第一,state 要在构造函数中定义
    this.state = {
      count: 0,
    }
  }
  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increase}>累加</button>
      </div>
    )
  }
  increase = () => {
    // 第二,不要直接修改 state ,使用不可变值 ----------------------------
    // this.state.count++ // 错误
    // this.setState({
    // 	count: this.state.count + 1, // SCU
    // });
    // 操作数组、对象的的常用形式

    // 第三,setState 可能是异步更新(有可能是同步更新) ----------------------------

    // this.setState(
    // 	{
    // 		count: this.state.count + 1,
    // 	},
    // 	() => {
    // 		// 联想 Vue $nextTick - DOM
    // 		console.log("count by callback", this.state.count); // 回调函数中可以拿到最新的 state
    // 	}
    // );
    // console.log("count", this.state.count); // 异步的,拿不到最新值

    // // setTimeout 中 setState 是同步的
    // setTimeout(() => {
    // 	this.setState({
    // 		count: this.state.count + 1,
    // 	});
    // 	console.log("count in setTimeout", this.state.count);
    // }, 0);

    // 自己定义的 DOM 事件,setState 是同步的。再 componentDidMount 中

    // 第四,state 异步更新的话,更新前会被合并 ----------------------------

    // 传入对象,会被合并(类似 Object.assign )。执行结果只一次 +1
    // this.setState({
    // 	count: this.state.count + 1,
    // });
    // this.setState({
    // 	count: this.state.count + 1,
    // });
    // this.setState({
    // 	count: this.state.count + 1,
    // });

    // 传入函数,不会被合并。执行结果是 +3
    this.setState((prevState, props) => {
      return {
        count: prevState.count + 1,
      }
    })
    this.setState((prevState, props) => {
      return {
        count: prevState.count + 1,
      }
    })
    this.setState((prevState, props) => {
      return {
        count: prevState.count + 1,
      }
    })
  }
  // bodyClickHandler = () => {
  // 	this.setState({
  // 		count: this.state.count + 1,
  // 	});
  // 	console.log("count in body event", this.state.count);
  // };
  // componentDidMount() {
  // 	// 自己定义的 DOM 事件,setState 是同步的
  // 	document.body.addEventListener("click", this.bodyClickHandler);
  // }
  // componentWillUnmount() {
  // 	// 及时销毁自定义 DOM 事件
  // 	document.body.removeEventListener("click", this.bodyClickHandler);
  // 	// clearTimeout
  // }
}

export default StateDemo

// -------------------------- 我是分割线 -----------------------------

// // 不可变值(函数式编程,纯函数) - 数组
// const list5Copy = this.state.list5.slice();
// list5Copy.splice(2, 0, "a"); // 中间插入/删除
// this.setState({
// 	list1: this.state.list1.concat(100), // 追加
// 	list2: [...this.state.list2, 100], // 追加
// 	list3: this.state.list3.slice(0, 3), // 截取
// 	list4: this.state.list4.filter((item) => item > 100), // 筛选
// 	list5: list5Copy, // 其他操作
// });
// // 注意,不能直接对 this.state.list 进行 push pop splice 等,这样违反不可变值

// // 不可变值 - 对象
// this.setState({
// 	obj1: Object.assign({}, this.state.obj1, { a: 100 }),
// 	obj2: { ...this.state.obj2, a: 100 },
// });
// // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值

image.png

this.setState异步还是同步?

setState 无所谓异步还是同步 看是否能命中 batchedUpdates 机制, Legacy模式命中异步,反之同步;Concurrent模式统一异步 判断 isBatchingUpdates

例:

  1. 合成事件中是异步
  2. 钩子函数中的是异步
  3. 原生事件中是同步
  4. setTimeout中是同步

哪些能命中 batchUpdate 机制

生命周期(和它调用的函数) React 中注册的事件(和它调用的函数) React 可以 “管理”的入口

哪些不能命中 batchUpdate 机制

setTimeout setInterval等(和它调用的函数) 自定义的 DOM 事件(和它调用的函数) React “管不到”的入口

transaction 事务机制

组件渲染和更新过程

JSX 如何渲染成页面 setState 之后如何更新页面 全流程 更新的两个阶段:reconciliation commit React fiber

表单校验

受控组件(非受控组件) input textarea select 用 value checkbox radio 用 checked

jsx
import React, { memo } from 'react'
import { Form, Input } from 'antd'

const Test = memo((props) => {
  const [form] = Form.useForm()

  const passwordValidator = (rule, value, callback) => {
    const { getFieldValue } = form
    if (value && value !== getFieldValue('password')) {
      callback('两次输入不一致!')
    }
    callback()
  }

  return (
    <Form from={form}>
      <Form.Item label='非空限制'>
        {getFieldDecorator('name', {
          rules: [
            {
              required: true,
              message: '不能为空',
            },
          ],
        })(<Input placeholder='请输入名称' />)}
      </Form.Item>
      <Form.Item label='字符串限制-范围限制'>
        {getFieldDecorator('password', {
          rules: [
            {
              required: true,
              message: '密码不能为空',
            },
            {
              min: 4,
              message: '密码不能少于4个字符',
            },
            {
              max: 6,
              message: '密码不能大于6个字符',
            },
          ],
        })(<Input placeholder='请输入密码' type='password' />)}
      </Form.Item>
      <Form.Item label='字符串限制-长度限制'>
        {getFieldDecorator('nickname', {
          rules: [
            {
              required: true,
              message: '昵称不能为空',
            },
            {
              len: 4,
              message: '长度需4个字符',
            },
          ],
        })(<Input placeholder='请输入昵称' />)}
      </Form.Item>
      <Form.Item label='自定义校验'>
        {getFieldDecorator('passwordcomfire', {
          rules: [
            {
              required: true,
              message: '请再次输入密码',
            },
            {
              validator: passwordValidator,
            },
          ],
        })(<Input placeholder='请输入密码' type='password' />)}
      </Form.Item>
      <Form.Item label='空格校验'>
        {getFieldDecorator('hobody', {
          rules: [
            {
              whitespace: true,
              message: '不能输入空格',
            },
          ],
        })(<Input placeholder='请输入昵称' />)}
      </Form.Item>
      <Form.Item label='正则校验'>
        {getFieldDecorator('qbc', {
          rules: [
            {
              message: '只能输入数字',
              pattern: /^[0-9]+$/,
            },
          ],
        })(<Input placeholder='请输入ABC' />)}
      </Form.Item>
    </Form>
  )
})

export default Test

组件使用

props 传递数据 props 传递函数 props 类型检查

异步加载

jsx
import ReactDOM from 'react-dom'
import React, { Component, lazy, Suspense } from 'react'

const Sub = lazy(() => import('./sub'))

class App extends Component {
  render() {
    return (
      <div>
        <Suspense fallback={<div>loading</div>}>
          <Sub />
        </Suspense>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

组件优化-经典面试题

jsx
import React, { Component, PureComponent, memo } from 'react'
import ReactDOM from 'react-dom'

// 组件的优化
class Sub extends PureComponent {
  // shouldComponentUpdate(nextProps, nextState) {
  // 	if (nextProps.name === this.props.name) {
  // 		return false;
  // 	}
  // 	return true;
  // }
  render() {
    console.log('sub render')
    return <div>sub</div>
  }
}

const SubFun = memo((props) => {
  console.log('SubFun render')
  return <div>SubFun</div>
})

class App extends Component {
  state = {
    count: 0,
  }
  handleClick = () => {
    this.setState({
      count: this.state.count + 1,
    })
  }

  callback = () => {}

  render() {
    console.log('render')
    const { count } = this.state
    return (
      <div>
        {/* <Sub name="wcd" /> */}
        {/* <Sub cb={() => this.callback()} /> */}
        <Sub cb={this.callback} />
        <SubFun cb={this.callback} />
        <p>{count}</p>
        <button onClick={this.handleClick}>button</button>
      </div>
    )
  }
}

ReactDOM.render(<App />, document.getElementById('root'))

React.FC

经常出现React.Fc这个函数, 比如我不使用React.Fc来处理组件的函数, 则在组件里面使用props.children会报错, 我们一起进入源码分析一下。

typescript
type FC<P = {}> = FunctionComponent<P>

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null

  propTypes?: WeakValidationMap<P> | undefined
  contextTypes?: ValidationMap<any> | undefined
  defaultProps?: Partial<P> | undefined
  displayName?: string | undefined
}
  1. FC 这个 type 接收一个参数 P , 默认值为空对象, 而这个 P
  2. FunctionComponent 就是个过度的名称而已, 可以认为 FC 就是 FunctionComponent
  3. 第一句意义为第一个参数为 PropsWithChildren<P> 类型, 第二个参数可有可无, 有则为任意类型, 返回 Reactdom 或者返回null。
  4. 后面四个参数不是必填, 我们主要研究第一句。

我们追查一下 PropsWithChildren

typescript
type PropsWithChildren<P> = P & { children?: ReactNode | undefined }

只是将传入的P的类型与{ children?: ReactNode | undefined } 合并而已, 看到这里我们就明白了, 其实用 React.FC 包裹一下是可以帮助 ts 推导出 props 身上可能有 children 属性。

HOC

高阶组件就是一个函数,接受一个组件作为参数,返回一个新的组件