VuePressVuePress
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目
vue2
vue3
React
css
javascript
实操题目
http
真题
事件循环
题目
  • 基本使用

基本使用

JSX 语法

变量

const pElem = <p>{this.state.name}</p>;
return pElem;

表达式

const exprElem = <p>{this.state.flag ? "yes" : "no"}</p>;
return exprElem;

class

const classElem = <p className="title">class set</p>;
return classElem;

style

// 1. 变量方式
const styleElem = <p style={{ color: "red" }}>class set</p>;
return styleElem;

// 2. 内联样式
const style = { color: "red" };
const styleElem = <p style={style}>class set</p>;
return styleElem;

原生 html

const rawHtml ='<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
  __html: rawHtml // 注意,必须是这种格式
}

const rawHtmlElem = <div>
  <p dangerouslySetInnerHTML={rawHtmlData}></p>
  <p>trawHtmly</p>
</div>
return rawHtmlElem

子元素

const imgElem = (
  <div>
    <p>我的头像</p>
    <img src={this.state.imgUrl} />
  </div>
);
return imgElem;

记载组件

const componentElem = (
  <div>
    <p>JSX 中加载一个组件</p>
    <hr />
    <List />
  </div>
);
return componentElem;

条件

if else

render () {
  if(this.state.theme == 'black'){
    return <p>1</p>
  }else {
    return <p>2</p>
  }
}

三元表达式

render () {
  return this.state.theme == 'black' ? <p>1</p> : <p>2</p>
}

逻辑运算符 || &&

render () {
  return  <div>
    { this.state.theme == 'black' && <div>1</div>}
    { (this.state.theme == 'black2' || this.state.theme == 'black3')  && <div>222</div>}
  </div>
}

列表渲染

map key

render () {
  return <div>
      {[1,2,3].map((c,index)=> {
        return <div key={index}>{c}</div>
      })}
  </div>
}

事件

bind this

import React from "react";

class EventDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "demo",
    };

    // 修改方法 this 指向, 当前方式 只会执行一次
    // this.openData2 = this.openData2.bind(this);
  }
  openData(key) {
    console.info(1, key);
  }
  openData2(key) {
    console.info(2, key);
  }
  openData3(key) {
    console.info(3, key);
  }

  render() {
    return (
      <div className="shopping-list">
        {/* 当前的bind this方式 会每次渲染都会执行 */}
        {/* <h1 onClick={this.openData('alert').bind(this)}>
          Shopping List for {this.state.name}
        </h1> */}

        {/* 现象:页面一加载就会 执行这个方法 */}
        {/* <h1 onClick={this.openData2('alert')}>
          Shopping List for {this.state.name}
        </h1> */}

        {/* 只有点击才会执行这个方法 */}
        {/* <h1 onClick={() => this.openData3('alert')}>
          Shopping List for {this.state.name}
        </h1> */}
      </div>
    );
  }
}

export default EventDemo;

关于 event

import React from "react";

class EventDemo extends React.Component {
  constructor(props) {
    super(props);

    this.clickEvent = this.clickEvent.bind(this);
  }

  clickEvent(event) {
    event.preventDefault(); // 阻止默认行为
    event.stopPropagation(); // 阻止冒泡

    console.info("event.target", event.target); // 指向当前元素,即当前元素出触发
    console.info("event.currentTarget", event.currentTarget); // 指向当前元素,假像

    // 当前event 是 react 封装过的, 打印出来 SyntheticBaseEvent
    console.info("event", event);
    console.info("event.__proto__.constructor", event.__proto__.constructor);

    // 原生的event: mouseEvent

    console.info("event.nativeEvent", event.nativeEvent); // 想要获取:原生的event:
    console.info("event.nativeEvent.target", event.nativeEvent.target); //   指向当前元素,即当前元素出触发
    console.info(
      "event.nativeEvent.currentTarget",
      event.nativeEvent.currentTarget,
    ); // document

    // 总结:
    // 1. event 是 SyntheticEvent, 模拟出来 DOM 事件所有能力
    // 2. event.nativeEvent 是原生事件对象
    // 3. 所有的事件,都被桂载到 document 上
    // 4. 和 DOM 事件不一样,和Vue 事件也不一样
  }

  render() {
    return (
      <div className="shopping-list">
        <a href="www.baidu.com" onClick={this.clickEvent}>
          百度地址
        </a>
      </div>
    );
  }
}

export default EventDemo;

传递自定义参数

import React from "react";

class EventDemo extends React.Component {
  testData(key, event) {
    console.log(key, event);
  }

  render() {
    return (
      <div className="shopping-list">
        <div onClick={(event) => this.testData("adb", event)}>参数</div>
      </div>
    );
  }
}

export default EventDemo;

表单

受控组件

  • input select textarea 用 value
  • checkbox radio 用 checked

受控组件中,内部状态用于跟踪元素值。当输入值改变时,React 会重新渲染输入

import React from "react";

class FormDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "demo",
    };
  }

  changeData(event) {
    this.setState({
      name: event.target.value,
    });
  }

  render() {
    return (
      <div>
        {this.state.name}
        <br />
        <label htmlFor="姓名:"></label>
        <input value={this.state.name} onChange={(e) => this.changeData(e)} />
      </div>
    );
  }
}

export default FormDemo;

非受控组件

使用场景:

  1. 必须手动操作 DOM 元素, setState 实现不了
  2. 文件上传 <input tye="file" />
  3. 富文本编辑器, 需要传入 DOM 元素

概念:不受控制的组件将 DOM 视为这些输入状态的真实源

  • ref

  • defaultValue defaultChecked

import React from "react";

class FormDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      phone: "123",
      flug: true,
    };
    this.InputRef = React.createRef();
  }

  openPhone = (e) => {
    alert(this.InputRef.current.value);
  };

  render() {
    return (
      <div>
        {this.state.phone}
        <br />
        <label htmlFor="属性值:"></label>
        <input defaultValue={this.state.phone} ref={this.InputRef} />
        <button onClick={this.openPhone}>alert phone</button>
        <hr />

        <input type="checkbox" defaultChecked={this.state.flug} />
      </div>
    );
  }
}

export default FormDemo;
  • 手动操作 DOM 元素
import React from "react";

class FormDemo extends React.Component {
  constructor(props) {
    this.inputFileRef = React.createRef();
  }

  alertFile = (e) => {
    alert(this.inputFileRef.current.files[0].name);
  };

  render() {
    return (
      <div>
        <input type="file" ref={this.inputFileRef} />
        <button onClick={this.alertFile}>alert file</button>
      </div>
    );
  }
}

export default FormDemo;

组件使用

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

import React from "react";
// import propTypes from 'prop-types';

// 父
class PropsDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      list: ["apple", "ios", "androd"],
    };
  }

  onSubmitList = (e) => {
    this.setState({
      list: this.state.list.concat(e),
    });
  };

  render() {
    return (
      <div>
        <InputDiv onSubmitList={this.onSubmitList} />
        <List list={this.state.list} />
      </div>
    );
  }
}

// 子
class InputDiv extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      title: "",
    };
  }

  getValue = (e) => {
    this.setState({
      title: e.target.value,
    });
  };

  addTodoList = () => {
    const { onSubmitList } = this.props;
    onSubmitList(this.state.title);
    this.setState({
      title: "",
    });
  };

  render() {
    return (
      <div>
        <input type="text" value={this.state.title} onChange={this.getValue} />
        <button onClick={this.addTodoList}>添加</button>
      </div>
    );
  }
}

// 子
class List extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    const { list } = this.props;
    return (
      <div>
        <ul>
          {list.map((c, i) => {
            return <li key={i}>{c}</li>;
          })}
        </ul>
      </div>
    );
  }
}
// 类型检查
// List.propTypes = {
//   list: propTypes.arrayOf(propTypes.string).isRequired,
// };

export default PropsDemo;

state 和 setState

不可变值

// 1. 不可变值 数组
this.list5Copy = this.state.list5.slice();
this.list5Copy.splice(2, 0, "a"); // 中间插入或者删除
this.setState({
  list1: this.state.list1.concat(100), // 追加 ok
  list2: [...this.state.list1, 100], // 追加 ok
  list3: this.state.list3.slice(0, 3), // 截取 ok
  list4: this.state.list3.filter((c) => c > 100), // 过滤 ok
  list5: list5Copy, // 其他操作 ok
});
// 注意: 不能直接对 this.state.list 进行 pop push splice等, 这样违反了不可变值

// 2. 不可变值 - 对象
// this.state.obj3.abd = 123; //错误写法
this.setState({
  obj1: Object.assign({}, this.state.obj1, { a: 1 }), // ok
  obj2: { ...this.state.obj1, a: 1 }, // ok
  // obj3: this.state.obj3, // error
});
// 注意: 不能直接对 this.state.obj 进行属性设置,这样违法不可变值

可能异步更新

根据 React 版本限制来的,<=17 Dom 事件 + 定时器 = 同步更新 React >18 的版本。 Dom 事件 + 定时器 又是异步了

  • 异步例子:
import React from "react";

class AsyncSetState extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 1,
    };
  }
  add = () => {
    this.setState({
      count: this.state.count + 1,
    });
    // 点击按钮, console还是旧的数据
    // setState 更新后, 没有立即渲染页面, 而是异步更新数据
    console.info("count", this.state.count); // 1 异步的, 拿不到最新的数据
  };

  // add = () => {
  //   this.setState(
  //     {
  //       count: this.state.count + 1,
  //     },
  //     () => {
  //      添加一个回调函数 相当于 vue $nextTick
  //       console.info('实时拿到最新的数据', this.state.count);
  //     }
  //   );
  //   console.info('count', this.state.count);
  // };

  render() {
    return (
      <div className="shopping-list">
        数字:{this.state.count}
        <button onClick={this.add}>增加</button>
      </div>
    );
  }
}

export default AsyncSetState;
  • 同步例子: 配置定时器使用
import React from "react";

class AsyncSetState extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  add = () => {
    setTimeout(() => {
      this.setState({
        count: this.state.count + 1,
      });
      console.info("count", this.state.count);
    }, 0);
  };

  render() {
    return (
      <div className="shopping-list">
        数字:{this.state.count}
        <button onClick={this.add()}>增加</button>
      </div>
    );
  }
}

export default AsyncSetState;
  • 同步例子: 自定义的 DOM 事件, setState 是同步,在 componentDidMount 绑定事件
import React from "react";

class AsyncSetState extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  componentDidMount() {
    document.body.addEventListener("click", () => {
      this.setState({
        count: this.state.count + 1,
      });
      console.info("count", this.state.count);
    });
  }

  render() {
    return (
      <div className="shopping-list">
        数字:{this.state.count}
        <button>增加</button>
      </div>
    );
  }
}

export default AsyncSetState;

结论:在 React 中,setState 的行为可能是同步的,也可能是异步的,这取决于它被调用的上下文 在 React 的生命周期方法或 React 合成事件处理器中调用 setState 时,React 会将多个 setState 调用批量处理,以提高性能。在这些情况下,setState 是异步的。 在 setTimeout、setInterval 或原生 DOM 事件处理器中调用 setState 时,React 无法控制这些情况,因此 setState 会立即更新 state,表现为同步。 这是因为 React 维护了一个名为 isBatchingUpdates 的变量,用于标识是否批量更新。在 React 可以控制的地方(例如 React 的生命周期事件和合成事件中),isBatchingUpdates 被设置为 true,setState 会执行异步操作。在 React 无法控制的地方(例如 setTimeout、setInterval 或直接在 DOM 上绑定原生事件等),isBatchingUpdates 被设置为 false,setState 会直接更新 state。

因此,当你在 setTimeout 中调用 setState 时,它会表现为同步更新 16。这就是为什么在 setTimeout 中,setState 会变成同步更新的原因。希望这个解释能帮助你理解这个问题。如果你还有其他问题,欢迎随时向我提问。😊

可能会被合并

  • 合并例子:
import React from "react";

class AsyncSetState extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  add = () => {
    // 传入对象,会被合并, 执行结果 只进行一次,
    // 本质还是异步, 执行第一条,不更新, 执行第二个,不更新,执行第三个,都不更新,当前执行完后, 开始变化,
    // 但是发现setState都没有发生改变,所有获取的数据全部旧的数据

    // this.setState({
    //   count: this.state.count + 1,
    // });
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // this.setState({
    //   count: this.state.count + 1,
    // });
    // console.info(23, this.state.count);

    // 传入函数, 不会被合并, 执行+2
    this.setState((pre, props) => {
      return { count: pre.count + 1 };
    });
    this.setState((pre, props) => {
      return { count: pre.count + 1 };
    });
    console.info(23, this.state.count);
  };
  render() {
    return (
      <div className="shopping-list">
        数字:{this.state.count}
        <button onClick={this.add}>增加</button>
      </div>
    );
  }
}

export default AsyncSetState;
  • 不合并例子:
// 传入函数, 不会被合并, 执行+2
this.setState((pre, props) => {
  return { count: pre.count + 1 };
});
this.setState((pre, props) => {
  return { count: pre.count + 1 };
});
console.info(23, this.state.count);
// 总结: 函数是一个可执行的代码

组件生命周期

组件生命周期

主要分为三个阶段 1. 挂载 2. 更新 3. 销毁

  1. 组件挂载时(组件状态的初始化,读取初始 state 和 props 以及两个生命周期方法,只会在初始化时运行一次)
  • componentWillMount 会在 render 之前调用(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state。)

  • componentDidMount 会在 render 之后调用

  1. 组件更新时(组件的更新过程是指父组件向下传递 props 或者组件自身执行 setState 方法时发生的一系列更新的动作)
  • 组件自身的 state 更新,依次执行

a. shouldComponentUpdate(会接收需要更新的 props 和 state,让开发者增加必要的判断条件,在其需要的时候更新,不需要的时候不更新。如果返回的是 false,那么组件就不再向下执行生命周期方法。) b. componentWillUpdate c. render(能获取到最新的 this.state) d. componentDidUpdate(能获取到最新的 this.state)

  • 父组件更新 props 而更新
  1. componentWillReceiveProps(在此调用 setState,是不会触发 re-render 的,而是会进行 state 的合并。因此此时的 this.state 不是最新的,在 render 中才可以获取更新后的 this.state。
  2. shouldComponentUpdate
  3. componentWillUpdate
  4. render
  5. componentDidUpdate

高级特性

函数组件、class 组件

// class 组件
import React from "react";
class ClassDemo extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div className="shopping-list">class components</div>;
  }
}
export default ClassDemo;
// 函数组件
function ClassDemo() {
  return <div className="shopping-list">class components</div>;
}
export default ClassDemo;

总结:

  1. 直观区别,函数组件代码量较少,相比类组件更加简洁
  2. 纯函数, 输入 props, 输出 jsx
  3. 没有实例,没有生命周期,没有 state
  4. 不能扩展其他方法

Refs

获取 dom 节点, 通过 .current 的方式

Protals 传送门

使用场景: 1. 一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。 2. fixed 需要放到 body 第一层

import React from "react";
import ReactDom from "react-dom";
class ClassDemo extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    // 1. 正常渲染
    // return <div className="shopping-list">class components</div>;
    //
    // 2. 使用 proTals 渲染到body上
    return ReactDom.createPortal(
      <div className="shopping-list">class components</div>,
      document.body,
    );
  }
}

export default ClassDemo;

context

使用场景:1. 公共信息(语言,主题等)如何传递给每个组件 2. 用 props 太繁琐 3. 用 redux 小题大做

例子:

  • context.js 公用处理的 共享数据
import React from "react";
const MyContext = React.createContext(null);
export default MyContext;
  • 父组件
import React from "react";
import ContextDemoChild from "./ContextDemoChild";
import MyContext from "./Context";

class ContextDemo extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: "dark",
    };
  }

  changeTheme = () => {
    this.setState({
      theme: this.state.theme === "dark" ? "white" : "dark",
    });
  };

  render() {
    return (
      <div className="shopping-list">
        <MyContext.Provider value={{ ...this.state }}>
          <ContextDemoChild />
        </MyContext.Provider>
        <button onClick={this.changeTheme}> 切换 theme</button>
      </div>
    );
  }
}

export default ContextDemo;
  • 子组件
import React from "react";
import ContextDemoChildSon from "./ContextDemoChildSon";
import MyContext from "./Context";

class ContextDemoChild extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <MyContext.Consumer>
          {(data) => {
            return (
              <>
                ContextDemoChild theme is {data.theme}
                <ContextDemoChildSon />
              </>
            );
          }}
        </MyContext.Consumer>
      </div>
    );
  }
}

export default ContextDemoChild;
  • 孙子组件
import React from "react";
import MyContext from "./Context";

class ContextDemoChildSon extends React.Component {
  render() {
    return (
      <div className="shopping-list">
        <MyContext.Consumer>
          {(data) => {
            return (
              <>
                ContextDemoChildSon theme is {data.theme}
                <br />
              </>
            );
          }}
        </MyContext.Consumer>
      </div>
    );
  }
}

export default ContextDemoChildSon;

注意事项: 因为 context 会根据引用标识来决定何时进行渲染(本质上是 value 属性值的浅比较),所以这里可能存在一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,由于 value 属性总是被赋值为新的对象,以下的代码会重新渲染下面所有的 consumers 组件:

解决方案:为了防止这种情况,将 value 状态提升到父节点的 state 里:

异步组件

import / React.lazy / Suspense

import React, { lazy, Suspense } from "react";
const LazyChild = lazy(() => import("./LazyChild"));

class ClassDemo extends React.Component {
  render() {
    return (
      <div>
        <Suspense fallback={<div>loading 。。。</div>}>
          <LazyChild />
        </Suspense>
      </div>
    );
  }
}

export default ClassDemo;

性能优化

shouldComponentUpdate(SCU)

  • React 默认情况下:父组件更新,子组件无条件更新

  • 如果你的组件只有当 props.color 或者 state.count 的值改变才需要更新时,你可以使用 shouldComponentUpdate 来进行检查

class CounterButton extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 1 };
  }

  shouldComponentUpdate(nextProps, nextState) {
    if (this.props.color !== nextProps.color) {
      return true;
    }
    if (this.state.count !== nextState.count) {
      return true;
    }
    return false;
  }

  render() {
    return (
      <button
        color={this.props.color}
        onClick={() => this.setState((state) => ({ count: state.count + 1 }))}
      >
        Count: {this.state.count}
      </button>
    );
  }
}
  • 例子 2: 避免 footer 组件重复渲染
import React from "react";

class AsyncSetState extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
  }

  add = () => {
    this.setState({
      count: this.state.count + 1,
    });
  };

  render() {
    return (
      <div className="shopping-list">
        数字:{this.state.count}
        <button onClick={this.add}>增加</button>
        <Footer />
      </div>
    );
  }
}

export default AsyncSetState;

class Footer extends React.Component {
  // 避免重复渲染
  shouldComponentUpdate(nextProps, nextState) {
    return false;
  }

  render() {
    console.info("footer console ");
    return <div className="shopping-list">footer {this.props.count}</div>;
  }
}

总结: 1. SCU 默认返回 true,React 默认重新渲染所有子组件 3. 可先不用 SCU,有性能问题在考虑使用

PureComponent

PureComponent 在 SCU 中进行 浅比较, 其实相当于 shouldComponentUpdate 的代码, 只不过简化写法了

class Footer extends React.PureComponent {
  render() {
    console.info("footer console ");
    return <div className="shopping-list">footer {this.props.count}</div>;
  }
}

React.memo

function MyComponent (props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps){
/*
如果把 nextProps 传入 render 方法的返回结果与将 prevProps 传入 render 方法的返回结果一致则返回 true,否则返回 false
*/
}
export default React.memo(MyComponent, areEqual)

不可变值 immutable.js

理论:1. 彻底拥抱 '不可变值' 2. 基于共享数据(不是深拷贝), 速度好 3. 有一定的学习成本,按需使用

高阶组件

  • 基本用法
// 高阶组件 不是一个功能,而是一种模式
class HOCFactory = (Component)=> {
 class HOC extends React.Component {
  render() {
    return <Component { ...this.props}/> // 返回拼装后的结果
  }
 }
 return HOC
}
  • 例子:
import React from "react";

const APP = (props) => {
  const { x, y } = props.mouse;
  return (
    <div>
      mouse position is x: {x}, y: {y}
    </div>
  );
};

// 高阶组件
const withMouse = (Component) => {
  class WithComponentMouse extends React.Component {
    constructor(props) {
      super(props);
      this.state = {
        x: 0,
        y: 0,
      };
    }

    handleMouseover = (e) => {
      this.setState({
        x: e.clientX,
        y: e.clientY,
      });
    };

    render() {
      return (
        <>
          <div
            style={{ widt: 300, height: 300, background: "red" }}
            onMouseMove={this.handleMouseover}
          >
            <Component {...this.props} mouse={this.state} />
          </div>
        </>
      );
    }
  }
  return WithComponentMouse;
};

export default withMouse(APP);

Render Props

// Render Props 的核心思想  通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件
class Factory extends React.Component {
  constructor () {
      this.state = {
          /* state 即多个组件的公共逻辑的数据*/
      }
  }
  /* 修改 state */
  render ({
    return <div>{this.props.render(this.state)}</div>
  }

  const App = () => (
      <Factory render={
          /* render 是一个函数组件*/
          (props)) => <p>{props.a} {props.b} ...</p>
      }/>
  }

useEffect 和 useLayoutEffect 异同

执行时机: useEffect 是在组件首次渲染/组件更新的时候异步执行的, 不会堵塞主线程 useLayoutEffect 在组件生成之后,浏览器渲染之前 执行, 会堵塞主线程

应用场景: useEffect:异步获取 ,组件卸载,清理订阅 useLayoutEffect:用于 DOM 更新后获取元素尺寸和位置

import { useRef, useLayoutEffect, useState } from "react";

function MyComponent() {
  const myRef = useRef(null);
  const [height, setHeight] = useState(0);

  useLayoutEffect(() => {
    const { height } = myRef.current.getBoundingClientRect();
    setHeight(height);
  }, []);

  return <div ref={myRef}>Hello, world!</div>;
}

react 18 有哪些更新

  1. 并发模式
  2. 自动批量处理
  3. suspense 支持 SSR
  4. startTransition
  5. useTransition
  6. useId
  7. 提供给第三方的hook

useMemo useCallback 的区别

  1. useMemo 目的:用来记忆计算结果,避免不必要的重复计算。 使用场景:当某个计算开销较大,并且在依赖项不变的情况下希望复用上一次的计算结果。

用法:

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

useMemo 将 computeExpensiveValue(a, b) 的结果缓存起来,只有当依赖项 [a, b] 变化时才会重新计算。

  1. useCallback 目的:用来记忆函数实例,避免函数在每次组件重新渲染时被重新创建。 使用场景:当函数被传递给子组件,并且子组件依赖这个函数来优化渲染性能时。

用法:

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

useCallback 返回一个记忆的回调函数,只有当依赖项 [a, b] 变化时才会重新创建这个函数。

Hooks 为什么出现

  1. 让函数组件有状态,处理副作用,能获取 ref,也能做数据缓存
  2. 解决逻辑复用难的问题
  3. 放弃面向对象编程,拥抱函数式编程

React 响应式原理

使用 diff 算法实现,React 在 state 或 props 改变时,会调用 render() 方法,生成一个虚拟 DOM 树,React 会将这棵树与上一次生成的树进行比较,找出其中的差异,并更新差异的部分

React Hooks 的底层原理

React Hooks 的底层原理是基于 Fiber 架构的调度机制和组件树的遍历实现的 文章介绍

Redux

通过 dispatch action 的方式传递到 reducers, 然后 action.type 修改数据,返回一个新的 state,然后通过 provider 的方式回显示 如果需要异步则通过 react-thunk, 里面有个 applymiddleware 的中间件

组件间的通信方式

  1. 父子组件:props 和 callback 方式
  2. 跨层级组件:context 上下文方式、ref 方式、event bus 事件总线
  3. 全局组件:react-redux 状态管理方式

react diff 算法

  1. 只比较同级,不跨级比较
  2. tag 不同,则直接删除重建,不再深度比较
  3. tag 和 key 俩者都相同,则认为是相同节点,不再深度比较

jsx 的本质

JSX 的本质是一种 JavaScript 的扩展语法,jsx 就是 createElement 函数,生成 vnode

const element = <h1>Hello, world!</h1>;
const element = React.createElement("h1", null, "Hello, world!");

合成事件

  • event 不是原生,是 SyntheticEvent 合成事件对象

好处

  • 更好的兼容性和跨平台
  • 载到 document,减少内存消耗,避免频繁解绑
  • 方便事件的统一管理
  • 减少内存消耗,提升性能,(不需要注册那么多的事件了,一种事件类型只在 Root 上注册一次)

React 事件机制和原生 DOM 事件流有什么区别

  1. react 中的事件是绑定到 document 上面的,
  2. 而原生的事件是绑定到 dom 上面的,
  3. 因此相对绑定的地方来说,dom 上的事件要优先于 document 上的事件执行

事务机制

什么是 fiber,fiber 解决了什么问题

将原来的树形结构(vdom)转换成 Fiber 链表的形式(child/sibling/return),整个 Fiber 的遍历是基于循环而非递归,可以随时中断。

fiber 架构其实是就是创建一种数据结构的组件数,在 react16 之前采用的同步递归更新,在遇到处理大批量数据或者复杂的 UI 的时候可能会导致主线程堵塞,后期引入的循环更新机制,可以在恰当的时候中断和重启任务,后期又引入的并发更新/批量处理/双缓存模式/时间切片

React 有哪几种方式改变 state

  1. this.forceUpdate
  2. this.setState

react 中 props 和 state 有什么区别

  1. props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)

React 中在哪捕获错误?

  1. ErrorBoundary
  2. window.error

ErrorBoundary

我们可以在整个应用范围内设置错误边界,也可以在各个组件上进行更细粒度的控制。 需要注意的是,错误边界只会捕获渲染时、生命周期方法和构造函数中的错误,但不会捕获以下错误:

事件处理(对于这种情况,需要使用常规的 try/catch 块) 异步代码(例如,setTimeout 或 requestAnimationFrame 回调函数) 服务端渲染 错误发生在错误边界本身而不是其子组件中时

为什么虚拟 dom 会提高性能?

虚拟 dom 相当于在 js 和真实 dom 中间加了一个缓存,利用 dom diff 算法避免了没有必要的 dom 操作,从而提高性能。

React18 有哪些更新?

  1. Suspense
  2. hook
  3. startTransition
  4. useTransition
  5. 更新 render API

React hook

  1. 解决了什么问题?
  2. 如何模拟生命周期?
  3. 如何自定义 hook?
  4. 性能优化?
  5. 使用 hook 遇到那些坑
  6. class 组件的缺点?

自定义 hook

/**
 * 倒计时 hook
 */

import { useEffect, useRef, useState } from "react";
// callback 是结束之后要执行什么事情
export default function useCountDown(initCount = 60, callBack = () => {}) {
  // 泛型
  const timeId = useRef < { id: number } > { id: -1 };
  const [count, setCount] = useState(initCount);

  const start = () => {
    setCount(initCount);
    // setInterval 在node 和 浏览器都有,所以要指定
    timeId.current.id = window.setInterval(() => {
      setCount((count) => count - 1);
    }, 1000);
  };
  useEffect(() => {
    // 清除副作用
    return () => {
      clearInterval(timeId.current.id);
    };
  }, []);

  useEffect(
    () => {
      // console.log(count, timeId.current)
      // 当倒计时为0时 清除定时器
      if (count <= 0) {
        clearInterval(timeId.current.id);
        callBack();
      }
    },
    [count], // 监听 count
  );

  return { count, start };
}

根据 JSX 的代码写出 Render 函数

<div className="container">
    <p onClick={onClick} data-name="р1">hello<b>{name}</b></р>
    <img src={imgSrc}/>
    <MyComponent title={title}></MyComponent>
</div>
const vnode = {
  tag: "div",
  props: {
    className: "container",
  },
  children: [
    // <p>
    {
      tag: "p",
      props: {
        dataset: {
          name: "p1",
        },
        on: {
          click: onClick,
        },
      },
      children: [
        "hello",
        {
          tag: "b",
          props: {},
          children: [name],
        },
      ],
    },
    //img
    {
      tag: "img",
      props: {
        src: imgSrc,
      },
      children: [],
    },

    //  <MyComponent title={title}></MyComponent>

    {
      tag: MyComponent,
      props: {
        title: title,
      },
      children: [],
    },
  ],
};

react router V6 版本的的 dataAPI 的 有哪些?

  1. loader 数据拦截等操作
  2. action 请求异步数据 (不能用 get 方法, 可以 post,put 等)
  3. errorElement errorElement={<ErrorBoundary />} 可以渲染错误 html
  4. lazy 异步加载数据

useReducer 干啥的?

useReducer 是 React 提供的一个用于状态管理的 Hook,它可以让我们更好地组织和管理组件的状态。

import { useReducer } from "react";

function reducer(state, action) {
  if (action.type === "incremented_age") {
    return {
      age: state.age + 1,
    };
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { age: 42 });

  return (
    <>
      <button
        onClick={() => {
          dispatch({ type: "incremented_age" });
        }}
      >
        Increment age
      </button>
      <p>Hello! You are {state.age}.</p>
    </>
  );
}

react router 页面传参数 有哪些? 刷新还存在吗?

path 动态路径、query、state

state:这种方式需要将参数放到 state 对象里面。例如: navigate("/page1", { state: {name:'Eula', age:"18"}})。然后你可以使用 useLocation() 来获取这些参数 useLocation 获取参数

扩展: 在 React Router 中,通过 state 传递的参数在刷新页面后是否还存在,取决于你使用的是哪种路由器 123:

如果你使用的是 <BrowserRouter>,那么刷新页面后,state 中的参数不会丢失 123。 如果你使用的是 <HashRouter>,那么刷新页面后,state 中的参数会丢失 123。 这是因为 <BrowserRouter> 使用 HTML5 的 history API 来保持 UI 和 URL 的同步,而 <HashRouter> 使用 URL 的哈希部分来保持 UI 和 URL 的同步。当你使用 <BrowserRouter> 时,state 参数是存储在 history 对象中的,所以即使刷新页面,这些参数也会被保留下来 123。但是,当你使用 <HashRouter> 时,由于哈希部分的内容不会被发送到服务器,所以刷新页面后,state 参数会丢失 123。

react 的 组件之间的通讯有哪些? 然后具体的使用场景?

props callback context redux

  • Props:这是最常见的 React 组件之间传递信息的方法。父组件通过 props 把数据传给子组件,子组件通过 this.props 去使用相应的数据 1。这种方式适用于父子组件间的通信,例如传递状态或回调函数等 1。

  • Instance Methods:父组件可以通过使用 refs 来直接调用子组件实例的方法,这种方式适用于父子组件间的通信,例如子组件是一个 modal 弹窗组件,子组件里有显示/隐藏这个 modal 弹窗的各种方法,我们就可以通过使用这个方法,直接在父组件上调用子组件实例的这些方法来操控子组件的显示/隐藏

  • Callback Functions:子组件通过调用父组件传来的回调函数,从而将数据传给父组件。这种方式适用于子组件向父组件通信,例如子组件需要修改父组件传递过来的数据,需要通过调用父组件的方法,在父组件中对数据进行修改

  • Event Bubbling:这种方法其实跟 React 本身没有关系,我们利用的是原生 DOM 元素的事件冒泡机制。这种方式适用于子组件向父组件通信,例如子组件的点击事件需要在父组件中被捕获处理

  • Parent Component:通过共同的父组件作为中介,利用父组件的状态进行传值。这种方式适用于兄弟组件之间的通信,例如两个兄弟组件需要共享某些状态,可以通过父组件来进行中转

  • Context:React 的 Context API 可以在组件树的任意层级进行传值。这种方式适用于跨层级组件通信,例如需要在多个组件之间共享一些全局状态,可以使用 Context API。

  1. react 的中的 context 的 value 传入数据后, 子组件要更新这个数据, 这么实现?
import React, { createContext, useState } from "react";

// 创建一个 Context 对象
const MyContext = createContext({
  data: null,
  setData: () => {},
});

function ParentComponent() {
  const [data, setData] = useState("initial data");

  // 把更新函数 setData 和数据 data 一起传递给 Provider
  return (
    <MyContext.Provider value={{ data, setData }}>
      <ChildComponent />
    </MyContext.Provider>
  );
}

function ChildComponent() {
  // 在子组件中,我们可以通过 useContext 获取到 setData 函数和 data 数据
  const { data, setData } = useContext(MyContext);

  const updateData = () => {
    setData("updated data");
  };

  return (
    <div>
      <p>{data}</p>
      <button onClick={updateData}>Update Data</button>
    </div>
  );
}

forwardRef

  • forwardRef 允许组件使用 ref 将 DOM 节点暴露给父组件。
import { forwardRef } from "react";
const MyInput = forwardRef(function MyInput(props, ref) {
  // ...
});

react 中如何实现路由监听?

  • hook
import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const MyComponent = () => {
  const location = useLocation();

  useEffect(() => {
    console.log(`The current URL is ${location.pathname}`);
  }, [location]);

  return <div>My Component</div>;
};

export default MyComponent;

  • class 方式
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
 
class MyComponent extends Component {
  constructor(props) {
    super(props);
    this.state = {
      route: props.location.pathname,
    };
  }
 
  componentDidMount() {
    this.unlisten = this.props.history.listen((location, action) => {
      this.setState({ route: location.pathname });
      // 这里可以添加你的路由监听逻辑
    });
  }
 
  componentWillUnmount() {
    // 清除监听器
    if (this.unlisten) {
      this.unlisten();
    }
  }
 
  render() {
    return (
      <div>
        Current route: {this.state.route}
      </div>
    );
  }
}
 
export default withRouter(MyComponent);

react 有哪几种创建组件的方式?

  1. react.createClass (经典方法)
  2. ES6 class
  3. 无状态函数

react hook 掌握哪些?

useState()
useEffect()
useContext()
useReducer()
useCallback()
useMemo()
useRef()
useLayoutEffect()

场景: 兄弟组件, 子子组件的 如何通讯?

context

兄弟组件共享一个状态, 然后这叫什么?

状态提升

<Router>组件有哪些?

<BrowserRouter>
<HashRouter>
<MemoryRouter>

React16 和 React18 区别有哪些? 具体体现在哪里?

React 16 和 React 18 之间有许多显著的区别,主要体现在性能提升、新功能引入和开发者体验改进等方面。以下是一些关键差异:

  1. 并发模式:React 18 引入了并发模式(Concurrent Mode),这是一种底层机制,允许 React 更高效地处理多个任务,从而提升应用的响应速度和用户体验。

  2. 自动批处理:React 18 实现了自动批处理(Automatic Batching),这意味着在一个事件循环中发生的所有状态更新将被自动批处理,从而减少不必要的重新渲染。

  3. useId Hook:React 18 新增了 useId Hook,用于生成唯一的 ID,这在需要唯一标识符的场景中非常有用。

  4. 性能改进:React 18 对内存使用和渲染性能进行了优化,提升了整体应用的性能。

  5. 开发者体验:React 18 提供了更好的错误处理和调试工具,使开发者能够更高效地进行开发和排查问题。

react 时间分片 有哪些改变? 17-18 有哪些改变?

React 17 和 React 18 在时间分片(Time Slicing)方面有一些重要的改进和变化:

  1. 时间分片的引入:React 17 引入了时间分片的概念,使得 React 可以将渲染工作分割成更小的任务块,并在浏览器的空闲时间执行这些任务,从而提高应用的响应速度。

  2. 并发特性:React 18 进一步优化了时间分片,通过引入并发特性(Concurrent Features),如 startTransition 和 useDeferredValue,使得开发者可以更好地管理复杂的状态更新,保持 UI 的响应性。

  3. 自动批处理:React 18 实现了自动批处理(Automatic Batching),这意味着在一个事件循环中发生的所有状态更新将被自动批处理,从而减少不必要的重新渲染。

  4. 改进的 Suspense 支持:React 18 对 Suspense 进行了改进,使其在处理异步数据加载时更加高效和灵活。

这些改进使得 React 18 在处理复杂应用和提升用户体验方面具有显著优势。如果你正在考虑升级,可以根据应用的具体需求和规模来决定是否进行升级。

useEffect 的认识?

useEffect 常用于数据获取、事件监听、订阅、手动 DOM 操作等场景

清理函数: useEffect 可以返回一个清理函数,用于在组件卸载或副作用重新执行前清理之前的副作用。这在处理订阅或计时器时特别有用

useEffect 配合 setTimeout 的 执行的顺序是什么?

useEffect(() => {
  const timer = setTimeout(() => {
    console.log("2 秒后执行");
  }, 2000);

  // 清理函数
  return () => clearTimeout(timer);
}, []);
  1. 组件渲染。

  2. useEffect 执行,设置一个 2 秒的定时器。

  3. 2 秒后,定时器回调函数执行,输出 “2 秒后执行”。

    如果组件在定时器触发前卸载,清理函数会被调用,取消定时器 12.

useEffect(() => {}, []) useEffect(() => {}) 这俩者的区别?

  1. useEffect(() => {}, []);:

    • 执行时机:这个 useEffect 只会在组件首次挂载(mount)时执行一次。
    • 依赖数组:空数组 [] 表示这个 useEffect 没有依赖项,因此它不会在后续的重新渲染时再次执行。
    • 用途:适用于只需要在组件挂载时执行一次的副作用,例如初始化数据加载 ¹。
    useEffect(() => {
      console.log("组件挂载时执行");
    }, []);
    
  2. useEffect(() => {});:

    • 执行时机:这个 useEffect 会在组件每次渲染后执行,包括首次挂载和每次更新(re-render)。
    • 没有依赖数组:没有提供依赖数组意味着每次组件重新渲染时,useEffect 都会执行。
    • 用途:适用于需要在每次渲染后执行的副作用,例如监听窗口大小变化 ²。
    useEffect(() => {
      console.log("每次渲染后执行");
    });
    

总结来说,useEffect(() => {}, []); 适用于只在组件挂载时执行一次的场景,而 useEffect(() => {}); 适用于每次组件渲染后都需要执行的场景。

Last Updated:
Contributors: zhanghusheng