前端react面试题详细解答

指出(组件)生命周期方法的不同
  • componentWillMount – 多用于根组件中的应用程序配置
  • componentDidMount – 在这可以完成所有没有 DOM 就不能做的所有配置,并开始获取所有你需要的数据;如果需要设置事件监听,也可以在这完成
  • componentWillReceiveProps – 这个周期函数作用于特定的 prop 改变导致的 state 转换
  • shouldComponentUpdate – 如果你担心组件过度渲染,shouldComponentUpdate 是一个改善性能的地方,因为如果组件接收了新的 prop, 它可以阻止(组件)重新渲染。shouldComponentUpdate 应该返回一个布尔值来决定组件是否要重新渲染
  • componentWillUpdate – 很少使用。它可以用于代替组件的 componentWillReceivePropsshouldComponentUpdate(但不能访问之前的 props)
  • componentDidUpdate – 常用于更新 DOM,响应 prop 或 state 的改变
  • componentWillUnmount – 在这你可以取消网络请求,或者移除所有与组件相关的事件监听器

fetch封装

npm install whatwg-fetch --save  // 适配其他浏览器
npm install es6-promise

export const handleResponse = (response) => {
  if (response.status === 403 || response.status === 401) {
    const oauthurl = response.headers.get('locationUrl');
    if (!_.isEmpty(oauthUrl)) {
      window.location.href = oauthurl;
      return;
    }
  }
  if (!response.ok) {
    return getErrorMessage(response).then(errorMessage => apiError(response.status, errorMessage));
  }
  if (isJson(response)) {
    return response.json();
  }
  if (isText(response)) {
    return response.text();
  }

  return response.blob();
};

const httpRequest = {
  request: ({
    method, headers, body, path, query,
  }) => {
    const options = {};
    let url = path;
    if (method) {
      options.method = method;
    }
    if (headers) {
      options.headers = {...options.headers,...headers};
    }
    if (body) {
      options.body = body;
    }
    if (query) {
      const params = Object.keys(query)
        .map(k => `${k}=${query[k]}`)
        .join('&');
      url = url.concat(`?${params}`);
    }
    return fetch(url, Object.assign({}, options, { credentials: 'same-origin' })).then(handleResponse);
  },
};

export default httpRequest;

React-Router 4怎样在路由变化时重新渲染同一个组件?

当路由变化时,即组件的props发生了变化,会调用componentWillReceiveProps等生命周期钩子。那需要做的只是: 当路由改变时,根据路由,也去请求数据:

class NewsList extends Component {
  componentDidMount () {
     this.fetchData(this.props.location);
  }

  fetchData(location) {
    const type = location.pathname.replace('/', '') || 'top'
    this.props.dispatch(fetchListData(type))
  }
  componentWillReceiveProps(nextProps) {
     if (nextProps.location.pathname != this.props.location.pathname) {
         this.fetchData(nextProps.location);
     } 
  }
  render () {
    ...
  }
}
复制代码

利用生命周期componentWillReceiveProps,进行重新render的预处理操作。

React.forwardRef是什么?它有什么作用?

React.forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。这种技术并不常见,但在以下两种场景中特别有用:

  • 转发 refs 到 DOM 组件
  • 在高阶组件中转发 refs

React的状态提升是什么?使用场景有哪些?

React的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的props把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是React单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。

概括来说就是将多个组件需要共享的状态提升到它们最近的父组件上在父组件上改变这个状态然后通过props分发给子组件。

一个简单的例子,父组件中有两个input子组件,如果想在第一个输入框输入数据,来改变第二个输入框的值,这就需要用到状态提升。

class Father extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
            Value1: '',
            Value2: ''
        }
    }
    value1Change(aa) {
        this.setState({
            Value1: aa
        })
    }
    value2Change(bb) {
        this.setState({
            Value2: bb
        })
    }
    render() {
        return (
            <div style={{ padding: "100px" }}>
                <Child1 value1={this.state.Value1} onvalue1Change={this.value1Change.bind(this)} />
                                <Child2 value2={this.state.Value1} />
            </div>
        )
    }
}
class Child1 extends React.Component {
    constructor(props) {
        super(props)
    }
    changeValue(e) {
        this.props.onvalue1Change(e.target.value)
    }
    render() {
        return (
            <input value={this.props.Value1} onChange={this.changeValue.bind(this)} />
        )
    }
}
class Child2 extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return (
            <input value={this.props.value2} />
        )
    }
}

ReactDOM.render(
    <Father />,
    document.getElementById('root')
)
复制代码

React.Children.map和js的map有什么区别?

JavaScript中的map不会对为null或者undefined的数据进行处理,而React.Children.map中的map可以处理React.Children为null或者undefined的情况。

React 中的高阶组件运用了什么设计模式?

使用了装饰模式,高阶组件的运用:

function withWindowWidth(BaseComponent) {
  class DerivedClass extends React.Component {
    state = {
      windowWidth: window.innerWidth,
    }
    onResize = () => {
      this.setState({
        windowWidth: window.innerWidth,
      })
    }
    componentDidMount() {
      window.addEventListener('resize', this.onResize)
    }
    componentWillUnmount() {
      window.removeEventListener('resize', this.onResize);
    }
    render() {
      return <BaseComponent {...this.props} {...this.state}/>
    }
  }
  return DerivedClass;
}
const MyComponent = (props) => {
  return <div>Window width is: {props.windowWidth}</div>
};
export default withWindowWidth(MyComponent);
复制代码

装饰模式的特点是不需要改变 被装饰对象 本身,而只是在外面套一个外壳接口。JavaScript 目前已经有了原生装饰器的提案,其用法如下:

@testable
   class MyTestableClass {
}

类的key改了,会发生什么,会执行哪些周期函数?

在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染。此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系,因此我们绝不可忽视转换函数中 Key 的重要性。

答:componentWillMount componentDidMount render

React中constructor和getInitialState的区别?

两者都是用来初始化state的。前者是ES6中的语法,后者是ES5中的语法,新版本的React中已经废弃了该方法。

getInitialState是ES5中的方法,如果使用createClass方法创建一个Component组件,可以自动调用它的getInitialState方法来获取初始化的State对象,

var APP = React.creatClass ({
  getInitialState() {
    return { 
        userName: 'hi',
        userId: 0
     };
 }
})
复制代码

React在ES6的实现中去掉了getInitialState这个hook函数,规定state在constructor中实现,如下:

Class App extends React.Component{
    constructor(props){
      super(props);
      this.state={};
    }
  }
复制代码

为什么 React 要用 JSX?

JSX 是一个 JavaScript 的语法扩展,或者说是一个类似于 XML 的 ECMAScript 语法扩展。它本身没有太多的语法定义,也不期望引入更多的标准。

其实 React 本身并不强制使用 JSX。在没有 JSX 的时候,React 实现一个组件依赖于使用 React.createElement 函数。代码如下:

class Hello extends React.Component {
  render() {
    return React.createElement(
        'div',
        null, 
        `Hello ${this.props.toWhat}`
      );
  }
}
ReactDOM.render(
  React.createElement(Hello, {toWhat: 'World'}, null),
  document.getElementById('root')
);
复制代码

而 JSX 更像是一种语法糖,通过类似 XML 的描述方式,描写函数对象。在采用 JSX 之后,这段代码会这样写:

class Hello extends React.Component {
  render() {
    return <div>Hello {this.props.toWhat}</div>;
  }
}
ReactDOM.render(
  <Hello toWhat="World" />,
  document.getElementById('root')
);
复制代码

通过对比,可以清晰地发现,代码变得更为简洁,而且代码结构层次更为清晰。

因为 React 需要将组件转化为虚拟 DOM 树,所以在编写代码时,实际上是在手写一棵结构树。而XML 在树结构的描述上天生具有可读性强的优势。

但这样可读性强的代码仅仅是给写程序的同学看的,实际上在运行的时候,会使用 Babel 插件将 JSX 语法的代码还原为 React.createElement 的代码。

总结: JSX 是一个 JavaScript 的语法扩展,结构类似 XML。JSX 主要用于声明 React 元素,但 React 中并不强制使用 JSX。即使使用了 JSX,也会在构建过程中,通过 Babel 插件编译为 React.createElement。所以 JSX 更像是 React.createElement 的一种语法糖。

React 团队并不想引入 JavaScript 本身以外的开发体系。而是希望通过合理的关注点分离保持组件开发的纯粹性。

为什么不直接更新 state 呢 ?

如果试图直接更新 state ,则不会重新渲染组件。

// 错误
This.state.message = 'Hello world';

需要使用setState()方法来更新 state。它调度对组件state对象的更新。当state改变时,组件通过重新渲染来响应:

// 正确做法
This.setState({message: ‘Hello World’});

对有状态组件和无状态组件的理解及使用场景

(1)有状态组件

特点:

  • 是类组件
  • 有继承
  • 可以使用this
  • 可以使用react的生命周期
  • 使用较多,容易频繁触发生命周期钩子函数,影响性能
  • 内部使用 state,维护自身状态的变化,有状态组件根据外部组件传入的 props 和自身的 state进行渲染。

使用场景:

  • 需要使用到状态的。
  • 需要使用状态操作组件的(无状态组件的也可以实现新版本react hooks也可实现)

总结: 类组件可以维护自身的状态变量,即组件的 state ,类组件还有不同的生命周期方法,可以让开发者能够在组件的不同阶段(挂载、更新、卸载),对组件做更多的控制。类组件则既可以充当无状态组件,也可以充当有状态组件。当一个类组件不需要管理自身状态时,也可称为无状态组件。

(2)无状态组件 特点:

  • 不依赖自身的状态state
  • 可以是类组件或者函数组件。
  • 可以完全避免使用 this 关键字。(由于使用的是箭头函数事件无需绑定)
  • 有更高的性能。当不需要使用生命周期钩子时,应该首先使用无状态函数组件
  • 组件内部不维护 state ,只根据外部组件传入的 props 进行渲染的组件,当 props 改变时,组件重新渲染。

使用场景:

  • 组件不需要管理 state,纯展示

优点:

  • 简化代码、专注于 render
  • 组件不需要被实例化,无生命周期,提升性能。 输出(渲染)只取决于输入(属性),无副作用
  • 视图和数据的解耦分离

缺点:

  • 无法使用 ref
  • 无生命周期方法
  • 无法控制组件的重渲染,因为无法使用shouldComponentUpdate 方法,当组件接受到新的属性时则会重渲染

总结: 组件内部状态且与外部无关的组件,可以考虑用状态组件,这样状态树就不会过于复杂,易于理解和管理。当一个组件不需要管理自身状态时,也就是无状态组件,应该优先设计为函数组件。比如自定义的 <Button/><Input /> 等组件。

类组件(Class component)和函数式组件(Functional component)之间有何不同
  • 类组件不仅允许你使用更多额外的功能,如组件自身的状态和生命周期钩子,也能使组件直接访问 store 并维持状态
  • 当组件仅是接收 props,并将组件自身渲染到页面时,该组件就是一个 ‘无状态组件(stateless component)’,可以使用一个纯函数来创建这样的组件。这种组件也被称为哑组件(dumb components)或展示组件

react 的优化

shouldcomponentUpdate pureCompoment setState

  • CPU的瓶颈(当有大量渲染任务的时候,js线程和渲染线程互斥)
  • IO的瓶颈 就是网络(如何在网络延迟客观存在的 情况下,减少用户对网络延 迟的感知)(Code Splitting • Data Fetching)比如react.lazy(组件懒加载) suspense(分包在网络上,用的时候在获取)
  • Virtual DOM 快么(Virtual DOM的优势不在于单次的操作,而是在大量、频繁的数据更新下,能够对视图 进行合理、高效的更新。)

展示组件(Presentational component)和容器组件(Container component)之间有何不同?

react 版本差异

react16.8 hooks

React 16之后有三个生命周期被废弃(但并未删除)

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

官方计划在17版本完全删除这三个函数,只保留UNSAVE_前缀的三个函数,目的是为了向下兼容,

react 16.4 新增

getSnapshotBeforeUpdate

getDerivedStateFromProps

对于废弃的生命周期函数,官方会采用逐步迁移的方式来实现版本的迁移:

16.3:为不安全的生命周期引入别名,UNSAFE_componentWillMount、UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用。)

未来 16.x 版本:为 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 启用废弃告警。(旧的生命周期名称和新的别名都将在这个版本中工作,但是旧的名称在开发模式下会产生一个警告。)

17.0:删除 componentWillMount、componentWillReceiveProps 和 componentWillUpdate。(在此版本之后,只有新的 “UNSAFE_” 生命周期名称可以使用。)。

在 React 中如何处理事件

为了解决跨浏览器的兼容性问题,SyntheticEvent 实例将被传递给你的事件处理函数,SyntheticEvent是 React 跨浏览器的浏览器原生事件包装器,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation()preventDefault()
比较有趣的是,React 实际上并不将事件附加到子节点本身。React 使用单个事件侦听器侦听顶层的所有事件。这对性能有好处,也意味着 React 在更新 DOM 时不需要跟踪事件监听器。

React组件的构造函数有什么作用?它是必须的吗?

构造函数主要用于两个目的:

  • 通过将对象分配给this.state来初始化本地状态
  • 将事件处理程序方法绑定到实例上

所以,当在React class中需要设置state的初始值或者绑定事件时,需要加上构造函数,官方Demo:

class LikeButton extends React.Component {
  constructor() {
    super();
    this.state = {
      liked: false
    };
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    this.setState({liked: !this.state.liked});
  }
  render() {
    const text = this.state.liked ? 'liked' : 'haven\'t liked';
    return (
      <div onClick={this.handleClick}>
        You {text} this. Click to toggle.      </div>
    );
  }
}
ReactDOM.render(
  <LikeButton />,
  document.getElementById('example')
);
复制代码

构造函数用来新建父类的this对象;子类必须在constructor方法中调用super方法;否则新建实例时会报错;因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法;子类就得不到this对象。

注意:

  • constructor () 必须配上 super(), 如果要在constructor 内部使用 this.props 就要 传入props , 否则不用
  • JavaScript中的 bind 每次都会返回一个新的函数, 为了性能等考虑, 尽量在constructor中绑定事件
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐