React常见问题汇总-灵析社区

JACKY

React作为流行的前端框架,我使用了3年左右的时间,从类组件到函数组件,ahook,antd,umi,next等在不同业务场景下使用了一些库。此篇记录React的基本使用和一些注意事项。 一些代码实现,我们可以在 codePen 中执行,测试。codepen

1. 类组件, class 继承React.Component或者React.PureComponent来实现类组件

类组件有生命周期,可通过 shouldComponentUpdate,PureComponent 来跳过更新。 状态: 可在 constructor 中定义状态,通过 setState 来修改状态,更新组件,不要直接修改 state。

class ClassComponent extends React.Component {
  render() {
    const { name, children, color } = this.props;
    return (
      <h1 className="title" style={{ color: color }}>
        {name}:{children}
      </h1>
    );
  }
}

2 函数组件:接受 props 属性,返回 react node。

在没hook之前,函数组件没有状态,只获取 props。 现在可用使用 useState,useEffect等hook 来处理状态。

function FunctionComponent(props) {
  return (
    <h1 className="title" style={{ color: props.color }}>
      {props.name}:{props.children}
    </h1>
  );
}

函数组件和类组件的区别

  1. 类组件有生命周期,可以通过 shouldComponentUpdatePureComponent 来跳过更新; 函数组件没有生命周期,可通过 useEffect 来模拟
  2. 类组件需要创建实例,是基于面向对象的方式编程;函数式组件不需要创建实例,接收输入,返回输出
  3. 可测试性: 函数式组件更方便编写单元测试
  4. 类组件有自己的实例,可以定义状态,而且可以修改状态更新组件; 函数式组件以前没有状态,可以使用 useState 使用状态

3. setState 调用 setState 可以修改状态,并且让组件刷新。面试会问:setState 是同步还是异步的?

class Counter extends React.Component {
  constructor(props) {
    super(props);
    //组件里可以定义状态对象
    this.state = { number: 0 };
  }
  handleClick = () => {
    //调用setState可以修改状态,并且让组件刷新
    // this.setState({ number: this.state.number + 1 });
    // console.log(event.currentTarget, event.target);
    //event.stopPropagation();
    this.setState({ number: this.state.number + 1 });
    console.log(this.state); //输出0
    this.setState({ number: this.state.number + 1 });
    console.log(this.state); //输出0
    setTimeout(() => {
      this.setState({ number: this.state.number + 1 });
      console.log(this.state); //输出2
      this.setState({ number: this.state.number + 1 });
      console.log(this.state); //输出3
    }, 50);
  };

  render() {
    return (
      <div id="counter">
        <p>number:{this.state.number}</p>
        <button onClick={this.handleClick}>+</button>
      </div>
    );
  }
}

State 的更新会被合并 当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state.

State 的更新可能是异步的;出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用 因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态 可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数.

setTimeout,Promise 中 setState 为啥是实时更新呢, setTimeout 是浏览器事件机制控制,脱离了 react 的上下文。在 React18 后就都是批量执行了。

4. 类组件生命周期

类组件的生命周期分为两版,16.8 版本之前的和之后的。16.8 版本之后的新增两个生命周期方法,删除了 3 个旧的方法。

新增静态方法 static getDerivedStateFromProps() getSnapshotBeforeUpdate() getDerivedStateFromProps 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。

getSnapshotBeforeUpdate() 在最近一次渲染输出(提交到 DOM 节点)之前调用。它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。此生命周期方法的任何返回值将作为参数传递给 componentDidUpdate()。

//父子组件的挂载和更新,卸载
//父组件
class Counter extends React.Component {
  static defaultProps = {
    name: "zhufeng", //定义默认属性
  };
  constructor(props) {
    super(props);
    this.state = { number: 0 };
    console.log("Counter 1.constructor");
  }
  componentWillMount() {
    console.log("Counter 2.componentWillMount");
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("Counter 5.shouldComponentUpdate");
    return nextState.number % 2 === 0; //偶数才更新
  }
  componentWillUpdate() {
    console.log("Counter 6.componentWillUpdate");
  }
  handleClick = () => {
    debugger;
    this.setState({ number: this.state.number + 1 });
  };

  render() {
    console.log("Counter 3.render");
    return (
      <div>
        <p>{this.state.number}</p>
        {this.state.number === 4 ? null : (
          <ChildCounter count={this.state.number} />
        )}
        <button onClick={this.handleClick}>+</button>
        {null}
      </div>
    );
  }
  componentDidUpdate() {
    console.log("Counter 7.componentDidUpdate");
  }
  componentDidMount() {
    console.log("Counter 4.componentDidMount");
  }
}
//子组件
class ChildCounter extends React.Component {
  componentWillUnmount() {
    console.log("ChildCounter 6.componentWillUnmount");
  }
  componentWillReceiveProps(newProps) {
    console.log("ChildCounter 4.componentWillReceiveProps");
  }
  componentWillMount() {
    console.log("ChildCounter 1.componentWillMount");
  }
  shouldComponentUpdate(nextProps, nextState) {
    console.log("ChildCounter 5.shouldComponentUpdate");

    return nextProps.count % 3 === 0;
  }
  render() {
    console.log("ChildCounter 2.render");
    return <div>{this.props.count}</div>;
  }
  componentDidMount() {
    console.log("ChildCounter 3.componentDidMount");
  }
}
//第一次生父子命周期执行顺序
"Counter 1.constructor"
"Counter 2.componentWillMount"
"Counter 3.render"
"ChildCounter 1.componentWillMount"
"ChildCounter 2.render"
"ChildCounter 3.componentDidMount"
"Counter 4.componentDidMount"
//点击 1 次
"Counter 5.shouldComponentUpdate"
//点击第 2 次
"Counter 5.shouldComponentUpdate"
"Counter 6.componentWillUpdate"
"Counter 3.render"
"ChildCounter 4.componentWillReceiveProps"
"ChildCounter 5.shouldComponentUpdate"
"Counter 7.componentDidUpdate"
//点击 3 次
"Counter 5.shouldComponentUpdate"
//点击第 4 次
"Counter 5.shouldComponentUpdate"
"Counter 5.shouldComponentUpdate"
"Counter 6.componentWillUpdate"
"Counter 3.render"
"ChildCounter 6.componentWillUnmount"
"Counter 7.componentDidUpdate"

函数组件使用 useEffect 实现生命周期,后面会介绍到。

5. react 中的事件流,分为原生事件流和合成事件流。

  • 事件捕获
  • 事件目标
  • 事件冒泡
  • 事件委托
  • 先绑定先执行

React 17 以前, 把事件委托到 document 对象上;

当真实 DOM 元素触发事件,先处理原生事件,然后会冒泡到 document 对象后,再处理 React 事件.React 事件绑定的时刻是在 reconciliation 阶段,会在原生事件的绑定前执行

  • 目的和优势
  • 进行浏览器兼容,React 采用的是顶层事件代理机制,能够保证冒泡一致性
  • 事件对象可能会被频繁创建和回收,因此 React 引入事件池,在事件池中获取或释放事件对象(React17 中被废弃)

React17 以前的执行顺序

document 捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
父元素 React 事件捕获
子元素 React 事件捕获
子元素 React 事件冒泡
父元素 React 事件冒泡
document 冒泡

React17 以后的执行顺序

document 原生捕获
父元素 React 事件捕获
子元素 React 事件捕获
父元素原生捕获
子元素原生捕获
子元素原生冒泡
父元素原生冒泡
子元素 React 事件冒泡
父元素 React 事件冒泡
document 原生冒泡

6.react hook介绍

Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。 react 16.8 版本以后,新增了 fiber 架构,提供了 hook api。 fiber 架构之前 react 更新是递归式的,中途是不能中断的,批量的更新会造成页面卡顿。有了 fiber 架构后,会分更新优先级,计算浏览器空余时间是否足够本次更新或者中断,下次去更新。

react 的更新优先级分为三类:

  1. 事件优先级:
  2. 更新优先级:
  3. 调度优先级:
  • fiber 使用 MessageChannel 来模拟 requestIdleCallback
  • requestIdleCallback 是浏览器的 api 方法,这个函数将在浏览器空闲时期被调用 var handle = window.requestIdleCallback(callback[, options])
const channel = new MessageChannel();
channel.port1.onmessage = function (event) {
  console.log("来自port2的消息:", event.data);
};
channel.port2.onmessage = function (event) {
  console.log("来自port1的消息:", event.data);
  channel.port2.postMessage("port2");
};
channel.port1.postMessage("port1");

hook 的使用规则

  • 只能在函数组件最外层使用,不可以在 if,循环等内部,或者子函数中使用
  • 只能在函数组件中使用,不能再类组件中使用。 也可以在自定义 hook 中使用(后面会有自定义 hook 的介绍)。

确保 Hook 在每一次渲染中都按照同样的顺序被调用。 由于 fiber 的结构是一个链表,双缓存的设计需要保持 currentFiber 和 workInporgressFiber 结构保持一致。

7. useState使用

数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字

function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount(count + 1)}>+</button>;
}

8. useEffect使用

可以使用useEffect模拟类组件的生命周期,如挂载,更新,卸载。

// 默认每次都调用的effect
useEffect(() => {
  document.title = `You clicked ${count} times`;
});
//   需要清除副作用的effect,return返回一个清除副作用的callback,模拟componentWillUnmount
useEffect(() => {
  console.log("开启一个定时器");
  const timer = setInterval(() => {
    setNumber((number) => number + 1);
  }, 1000);
  return () => {
    console.log("销毁老的定时器");
    clearInterval(timer);
  };
});

// 模拟componentDidUpdate 生命周期
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新

// 如果想只执行依次,useEffect第二个参数可传一个空数组[],告诉react不依赖props和state中的值。effect内部会一直保留初始值。
useEffect(() => {
  document.title = `You clicked 1 times`;
}, []);

9. useReducer的使用

传入一个纯函数reducer和初始值,返回state和dispatch方法。熟悉redux的同学应该比较容易理解

function reducer(state = { number: 0 }, action) {
  switch (action.type) {
    case 'ADD':
      return { number: state.number + 1 };
    case 'MINUS':
      return { number: state.number - 1 };
    default:
      return state;
  }
}
function App() {
  const [state, dispatch] = React.useReducer(reducer, { number: 0 });
  return (
    <div>
      <p> Counter:{state.number}</p>
      <button onClick={() => dispatch({ type: 'ADD' })}>+</button>
      <button onClick={() => dispatch({ type: 'MINUS' })}>-</button>
    </div>
  )
}

10. useLayoutEffect的使用

与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。尽可能使用标准的 useEffect 以避免阻塞视觉更新。

function Animation() {
  const ref = React.useRef();//React.createRef() = {current:null}
  React.useLayoutEffect(() => {
    ref.current.style.transition = `all 500ms`;
    ref.current.style.transform = `translate(500px)`;
  });
  let style = {
    width: '100px',
    height: '100px',
    borderRadius: '50%',
    backgroundColor: 'red'
  }
  return (
    <div style={style} ref={ref}></div>
  )
}

11. useContext的使用

React.createContext()创建一个Context;useContext 接收一个 context 对象,返回传递的值,由Context.Provider 的value决定

12.useRef的使用

返回一个可变的 ref 对象,.current被赋值;useRef 会在每次渲染时返回同一个 ref 对象。主动改变.current不会引发重新渲染。

  function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );

// ref保存值
  function Counter() {
  const valueRef = React.useRef();
  const [state, setState] = React.useState(0);
  const handleClick = () => {
    let newValue = state + 1;
    valueRef.current = newValue;
    setState(newValue);
    getNewValue();
  }
  const getNewValue = () => {
    console.log(valueRef.current);
  }
  return (
    <div>
      <p>{state}</p>
      <button onClick={handleClick}>+</button>
      <button onClick={getNewValue}>获取最新的状态</button>
    </div>
  )
}
}


13. useMemo

返回一个memoized 的值,依赖项变化的时候才重新计算值。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。useMemo存储的是一个值。

  let data = React.useMemo(() => ({ number }), [number]);

14. useCallback

返回一个memoized 回调函数,依赖变化的时候才会更新,useCallback存储的是一个函数,当依赖变化时,生成新的。

  let handleClick = React.useCallback(() => setNumber(number + 1), [number])

15. 自定义 hook

规定以名字use开头的函数,根据情况决定传参和返回

function useSlefStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  return isOnline;
}

本章节介绍了 react 类组件和函数组件的使用以及一些基本原理,另外还有高阶组件,react-router路由管理,redux状态管理会在后面的文章中介绍。

阅读量:2007

点赞量:0

收藏量:0