REACT V16.8 中加入了 React.FC 配合typescript 使用

一,函数式组件

  • 纯函数
  • 输入props,输出JSX
  • 没有实例
  • 没有生命周期
  • 没有state
  • 不能扩展其它方法
  1. function List (props) {
  2. const { text } = this.props
  3. return (
  4. <div>{text}</div>
  5. )
  6. }

无状态组件的性能比较高,因为它仅是一个函数,而普通组件是一个class

在越来越多的复杂的函数组件的情况下的时候;我们有时候需要的简单的组件;但是需要使用到一些他们的状态的保存和一些生命周期的比变化的 时候我们又不想去使用class的场景的组件;这时候需要怎么办呢;

于是乎在react的16.8中的时候react更新了关于react hooks 的一些东西;

简单的说就是在无状态组件中可以使用生命周期的一些方法;从而使得不需要很重量的代码就可以达到类中的状态管理

名称 解释 写法
useState 用于为函数组件引入状态(state)。纯函数不能有状态,所以把状态放在钩子里面。 const [on, setOn] = useState(false) 状态名改变状态的方法状态的默认值
useContext() 如果需要在组件之间共享状态
useReducer() action 钩子
React 本身不提供状态管理功能,通常需要使用外部库。这方面最常用的库是 Redux组件发出 action 与状态管理器通信。状态管理器收到 action 以后,使用 Reducer 函数算出新的状态,Reducer 函数的形式是(state, action) => newState const [state, dispatch] = useReducer(reducer, initialState); 上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
useEffect()副作用钩子 useEffect()用来引入具有副作用的操作,最常见的就是向服务器请求数据。以前,放在componentDidMount里面的代码,现在可以放在useEffect()。 每当组件参数personId发生变化,useEffect()就会执行。组件第一次渲染时,useEffect()也会执行。 上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()
useMemo() 是定义一段函数逻辑是否重复执行 若第二个参数为空,则每次渲染组件该段逻辑都会被执行,就不会根据传入的属性值来判断逻辑是否重新执行,这样写useMemo()也就毫无意义。 useMemo()是需要有返回值的,并且返回值是直接参与渲染,因此useMemo()是在渲染期间完成的。
useRef() 是因为函数组件没有实例,所以函数组件中无法使用String Ref、Callback Ref、Create Ref,取而代之的是useRef
获取DOM元素的节点
获取子组件的实例
渲染周期之间共享数据的存储(state不能存储跨渲染周期的数据,因为state的保存会触发组件重渲染)
useImperativeHandle
useLayoutEffect
useDebugValue

useState explame

  1. // useState explame
  2. import React, { useState } from "react";
  3. export default function Button() {
  4. const [buttonText, setButtonText] = useState("Click me, please");
  5. // 状态名 改变状态的方法 => 以及对应该状态名的默认的值
  6. // 调用该状态改变的方法,赋值新的状态
  7. function handleClick() {
  8. return setButtonText("Thanks, been clicked!");
  9. }
  10. return <button onClick={handleClick}>{buttonText}</button>;
  11. }

useContext() explame

  1. import React from "react";
  2. // 从react中获取对应的appcontent的组件
  3. const {AppContext,Consumer} = React.createContext({});
  4. constructor (props) {
  5. super(props)
  6. this.state = { theme: 'light' }
  7. }
  8. // AppContext.Provider 包裹在对应的函数中;对应的全局共享的字面量存储在value中
  9. <AppContext.Provider value={{
  10. username: 'superawesome'
  11. }}>
  12. <div className="App">
  13. <Navbar/>
  14. <Messages/>
  15. </div>
  16. </AppContext.Provider>
  17. // 在对应的子组件中通过useContext(AppContext) 获取对应的值
  18. const Navbar = () => {
  19. const { username } = useContext(AppContext);
  20. return (
  21. <div className="navbar">
  22. <p>AwesomeSite</p>
  23. <p>{username}</p>
  24. </div>
  25. );
  26. }
  27. // 子组件中调用
  28. import { Consumer } from "./index";//引入父组件的Consumer容器
  29. render () {
  30. return (
  31. // Consumer 容器,可以拿到上文传递下来的 theme 属性,并可以展示对应的值
  32. <Consumer>
  33. { theme => <div>子组件。获取父组件的值: {theme} </div> }
  34. </Consumer>
  35. )
  36. }

useReducer() explame

  1. import myReducer from ”Reducer“
  2. function App() {
  3. // 它接受 Reducer 函数和状态的初始值作为参数,
  4. // 返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
  5. const [state, dispatch] = useReducer(myReducer, { count: 0 });
  6. return (
  7. <div className="App">
  8. <button onClick={() => dispatch({ type: 'countUp' })}>
  9. +1
  10. </button>
  11. <p>Count: {state.count}</p>
  12. </div>
  13. );
  14. }

useEffect() explame

上面用法中,useEffect()接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()

  1. const Person = ({ personId }) => {
  2. const [loading, setLoading] = useState(true);
  3. const [person, setPerson] = useState({});
  4. useEffect(() => {
  5. setLoading(true);
  6. fetch(`https://swapi.co/api/people/${personId}/`)
  7. .then(response => response.json())
  8. .then(data => {
  9. setPerson(data);
  10. setLoading(false);
  11. });
  12. }, [personId])
  13. if (loading === true) {
  14. return <p>Loading ...</p>
  15. }
  16. return <div>
  17. <p>You're viewing: {person.name}</p>
  18. <p>Height: {person.height}</p>
  19. <p>Mass: {person.mass}</p>
  20. </div>
  21. }

useMemo() explame

若第二个参数为空,则每次渲染组件该段逻辑都会被执行,就不会根据传入的属性值来判断逻辑是否重新执行,这样写useMemo()也就毫无意义。

  1. import React, { memo, useState, useMemo } from "react";
  2. function App() {
  3. const [value, setValue] = useState(0);
  4. const increase = useMemo(() => {
  5. if(value > 2) return value + 1;
  6. }, [value]);
  7. return (
  8. <div>
  9. <Child value={value} />
  10. <button
  11. type="button"
  12. onClick={() => {
  13. setValue(value + 1);
  14. }}
  15. >
  16. value:{value},increase:{increase || 0}
  17. </button>
  18. </div>
  19. );
  20. }
  21. // memo() 定义一个组件是否需要重复执行
  22. const Child = memo(function Child(props) {
  23. console.log('Child render')
  24. return <h1>value:{props.value}</h1>;
  25. });
  26. export default App;

Refs

React 的核心思想是每次对于界面 state 的改动,都会重新渲染整个Virtual DOM,然后新老的两个 Virtual DOM 树进行 diff(协调算法),对比出变化的地方,然后通过 render 渲染到实际的UI界面,
使用 Refs 为我们提供了一种绕过状态更新和重新渲染时访问元素的方法;这在某些用例中很有用,但不应该作为 props 和 state 的替代方法。
在项目开发中,如果我们可以使用 声明式 或 提升 state 所在的组件层级(状态提升) 的方法来更新组件,最好不要使用 refs。

函数组件中 useRef() explame

  1. import React, { useEffect, useRef } from 'react';
  2. function App() {
  3. const h1Ref = useRef();
  4. useEffect(() => {
  5. console.log('useRef')
  6. console.log(h1Ref.current)
  7. }, [])
  8. return <h1 ref={h1Ref}>Hello World!</h1>
  9. }
  10. export default App;

管理焦点(如文本选择)或处理表单数据: Refs 将管理文本框当前焦点选中,或文本框其它属性。
在大多数情况下,我们推荐使用受控组件来处理表单数据。在一个受控组件中,表单数据是由 React 组件来管理的,每个状态更新都编写数据处理函数。另一种替代方案是使用非受控组件,这时表单数据将交由 DOM 节点来处理。要编写一个非受控组件,就需要使用 Refs 来从 DOM 节点中获取表单数据。

  1. // 引出了非受控组件 获取dom节点
  2. class NameForm extends React.Component {
  3. constructor(props) {
  4. super(props);
  5. this.input = React.createRef();
  6. }
  7. handleSubmit = (e) => {
  8. console.log('A name was submitted: ' + this.input.current.value);
  9. e.preventDefault();
  10. }
  11. render() {
  12. return (
  13. <form onSubmit={this.handleSubmit}>
  14. <label>
  15. Name:
  16. <input type="text" ref={this.input} />
  17. </label>
  18. <input type="submit" value="Submit" />
  19. </form>
  20. );
  21. }
  22. }
  • 因为非受控组件将真实数据储存在 DOM 节点中,所以在使用非受控组件时,有时候反而更容易同时集成 React 和非 React 代码。如果你不介意代码美观性,并且希望快速编写代码,使用非受控组件往往可以减少你的代码量。否则,你应该使用受控组件。

  • 媒体播放:基于 React 的音乐或视频播放器可以利用 Refs 来管理其当前状态(播放/暂停),或管理播放进度等。这些更新不需要进行状态管理。
  • 触发强制动画:如果要在元素上触发过强制动画时,可以使用 Refs 来执行此操作。threejs
  • 集成第三方 DOM 库


  1. import React from 'react';
  2. // 不同于传递 createRef() 创建的 ref 属性,你会传递一个函数。
  3. // 这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问。
  4. export default class Hello extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.textRef = null; // 创建 ref 为 null
  8. }
  9. componentDidMount() {
  10. // 注意:这里没有使用 "current"
  11. // 直接使用原生 API 使 text 输入框获得焦点
  12. this.textRef.focus();
  13. }
  14. render() {
  15. // 把 <input> ref 关联到构造器里创建的 textRef 上
  16. return <input ref={node => this.textRef = node} />
  17. }
  18. }

React 将在组件挂载时将 DOM 元素传入ref 回调函数并调用,当卸载时传入 null 并调用它。在 componentDidMount 或 componentDidUpdate 触发前,React 会保证 refs 一定是最新的。
像上例, ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。
这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。我们可以通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

  1. export default class Hello extends React.Component {
  2. constructor(props) {
  3. super(props);
  4. }
  5. componentDidMount() {
  6. // 通过 this.refs 调用
  7. // 直接使用原生 API 使 text 输入框获得焦点
  8. this.refs.textRef.focus();
  9. }
  10. render() {
  11. // 把 <input> ref 关联到构造器里创建的 textRef 上
  12. return <input ref='textRef' />
  13. }
  14. }

尽管字符串 stringRef 使用更方便,但是它有一些缺点,因此严格模式使用 stringRef 会报警告。官方推荐采用回调 Refs。

useRef() 比 ref 属性更有用。useRef() Hook 不仅可以用于 DOM refs, useRef() 创建的 ref 对象是一个 current 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。

  1. function Timer() {
  2. const intervalRef = useRef();
  3. useEffect(() => {
  4. const id = setInterval(() => {
  5. // ...
  6. });
  7. intervalRef.current = id;
  8. return () => {
  9. clearInterval(intervalRef.current);
  10. };
  11. });
  12. // ...
  13. }

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current: …}对象的唯一区别是,useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

createRef 源码解析

  1. // ReactCreateRef.js 文件
  2. import type {RefObject} from 'shared/ReactTypes';
  3. // an immutable object with a single mutable value
  4. export function createRef(): RefObject {
  5. const refObject = {
  6. current: null,
  7. };
  8. if (__DEV__) {
  9. // 封闭对象,阻止添加新属性并将所有现有属性标记为不可配置。当前属性的值只要可写就可以改变。
  10. Object.seal(refObject);
  11. }
  12. return refObject;
  13. }
  14. ...
  15. export type RefObject = {
  16. current: any,
  17. };

forwardRef

在 React.createRef 中已经介绍过,有三种方式可以使用 React 元素的 ref
ref 是为了获取某个节点的实例,但是 函数式组件(PureComponent) 是没有实例的,不存在 this的,这种时候是拿不到函数式组件的 ref 的。

为了解决这个问题,由此引入 React.forwardRef, **React.forwardRef** 允许某些组件接收 ref,并将其向下传递给 子组件

  1. const ForwardInput = React.forwardRef((props, ref) => (
  2. <input ref={ref} />
  3. ));
  4. class TestComponent extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. this.inputRef = React.createRef(); // 创建 ref 存储 textRef DOM 元素
  8. }
  9. componentDidMount() {
  10. this.inputRef.current.value = 'forwardRef'
  11. }
  12. render() {
  13. return ( // 可以直接获取到 ForwardInput input 的 ref:
  14. <ForwardInput ref={this.inputRef}>
  15. )
  16. }
  17. }
  • 只在使用 React.forwardRef 定义组件时,第二个参数 ref 才存在
  • 在项目中组件库中尽量不要使用 React.forwardRef ,因为它可能会导致子组件被 破坏性更改
  • 函数组件 和 class 组件均不接收 **ref** 参数 ,即 props 中不存在 ref,ref 必须独立 props 出来,否则会被 React 特殊处理掉。
  • 通常在 高阶组件(HOC) 中使用 **React.forwardRef**

**<br />

  1. function enhance(WrappedComponent) {
  2. class Enhance extends React.Component {
  3. componentWillReceiveProps(nextProps) {
  4. console.log('Current props: ', this.props);
  5. console.log('Next props: ', nextProps);
  6. }
  7. render() {
  8. const {forwardedRef, ...others} = this.props;
  9. // 将自定义的 prop 属性 “forwardedRef” 定义为 ref
  10. return <WrappedComponent ref={forwardedRef} {...others} />;
  11. }
  12. }
  13. // 注意 React.forwardRef 回调的第二个参数 “ref”。
  14. // 我们可以将其作为常规 prop 属性传递给 Enhance,例如 “forwardedRef”
  15. // 然后它就可以被挂载到被 Enhance 包裹的子组件上。
  16. return React.forwardRef((props, ref) => {
  17. return <Enhance {...props} forwardedRef={ref} />;
  18. });
  19. }
  20. // 子组件
  21. class MyComponent extends React.Component {
  22. focus() {
  23. // ...
  24. }
  25. // ...
  26. }
  27. // EnhancedComponent 会渲染一个高阶组件 enhance(MyComponent)
  28. const EnhancedComponent = enhance(MyComponent);
  29. const ref = React.createRef();
  30. // 我们导入的 EnhancedComponent 组件是高阶组件(HOC)Enhance。
  31. // 通过React.forwardRef 将 ref 将指向了 Enhance 内部的 MyComponent 组件
  32. // 这意味着我们可以直接调用 ref.current.focus() 方法
  33. <EnhancedComponent
  34. label="Click Me"
  35. handleClick={handleClick}
  36. ref={ref}
  37. />;

forwardRef 与 Hook useImperativeHandle

useImperativeHandle 可以让你在使用 ref 时自定义暴露给父组件的实例值。在大多数情况下,应当避免使用 ref 这样的命令式代码。useImperativeHandle 应当与 forwardRef一起使用:

  1. function FancyInput(props, ref) {
  2. const inputRef = useRef();
  3. useImperativeHandle(ref, () => ({
  4. focus: () => {
  5. inputRef.current.focus();
  6. }
  7. }));
  8. return <input ref={inputRef} ... />;
  9. }
  10. FancyInput = forwardRef(FancyInput);

在本例中,渲染 <FancyInput ref={fancyInputRef} /> 的父组件可以调用 fancyInputRef.current.focus()。

源码解读

  1. export default function forwardRef<Props, ElementType: React$ElementType>(
  2. render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
  3. ) {
  4. if (__DEV__) {
  5. if (render != null && render.$$typeof === REACT_MEMO_TYPE) {
  6. warningWithoutStack(
  7. false,
  8. 'forwardRef requires a render function but received a `memo` ' +
  9. 'component. Instead of forwardRef(memo(...)), use ' +
  10. 'memo(forwardRef(...)).',
  11. );
  12. } else if (typeof render !== 'function') {
  13. warningWithoutStack(
  14. false,
  15. 'forwardRef requires a render function but was given %s.',
  16. render === null ? 'null' : typeof render,