React 500 问

808 阅读21分钟

fork by github.com/sudheerj/re…
即是对上面链接的翻译,也是加深自己的理解 + 部分补充(斜体)
持续更新:www.yuque.com/3crazyone/c…

React 核心

1. 什么是 React

是一个开源用于构建用户页面,特别是单页应用的 JavaScript 库,主要专注于 Web 和移动应用的视图层。一开始是 facebook 工程师 Jordan Walke 负责开发,2011 年首次用于 facebook 的新闻推送中,并在 2012 年首次应用于 instagram。

2. React 的主要特性是什么

  • 使用虚拟 DOM 代替真实 DOM, 减少 DOM 操作开销
  • 遵循单向数据流/数据绑定
  • 使用可复用/可组合的 UI 组件来开发视图
  • 支持服务端渲染

3. 什么是 JSX

JSX 是基于 ECMAScript 的类 XML 的语法扩展,是为 React.createElement() 函数提供语法糖,这样我们能在 JS 是使用 HTML 模版的形式进行开发,更容易被理解。

在下面的示例中,<h1> 标记内的文本作为 JavaScript 函数返回到 render 函数。

class App extends React.Component {
  render() {
    return(
      <div>
        <h1>{'Welcome to React world!'}</h1>
      </div>
    )
  }
}

4. 元素(Element)和组件(Component)之间的区别是什么

元素是一个普通对象,它根据 DOM 节点或其他组件描述希望在屏幕上显示的内容。元素可以在其 props 中包含其他元素。创建一个 React 元素很简单。一旦创建了一个元素,它就不会发生改变。

React 元素的对象表示如下:

const element = React.createElement(
  'div',
  {id: 'login-btn'},
  'Login'
)

React.createElement 的作用是返回一个对象:

{
  type: 'div',
  props: {
    children: 'Login',
    id: 'login-btn'
  }
}

最后,它使用 ReactDOM.render() 呈现给 DOM:

<div id='login-btn'>Login</div>

而组件可以用几种不同的方式声明。它可以是一个带有 render() 方法的类(class)。或者,在简单的情况下,可以将它定义为一个函数。在这两种情况下,它都接受 props 作为输入,并返回一个 JSX 结构作为输出

const Button = ({ onLogin }) =>
  <div id={'login-btn'} onClick={onLogin}>Login</div>

然后 JSX 转换为一个通过 React.createElement() 进行转换为:

const Button = ({ onLogin }) => React.createElement(
  'div',
  { id: 'login-btn', onClick: onLogin },
  'Login'
)

5. 如何创建一个组件

创建组件有两种方式:

  • 函数式组件:这是一种简单的创建组件的方式。就像个简单的 JS 函数,它接收 props 作为第一个参并且返回 React 元素:
function Greeting({ message }) {
  return <h1>{`Hello, ${message}`}</h1>

}
  • 类组件:也可以使用 ES6 class 的语法糖来定义一个组件,上面的函数组件用类组件来写的话:
class Greeting extends React.Component {
  render() {
    return <h1>{`Hello, ${this.props.message}`}</h1>
  }
}

6. 何时使用类组件而不是函数组件

如果组件需要使用 state 或生命周期方法的时候应该用类组件,否则使用函数组件,但是, React 16.8 加入了 hooks, 所以函数组件也可以使用 state, 生命周期和其他特性了。

7. 什么是 Pure Component

React.PureComponent 其实和 React.Component 一样,不同在于它处理了 shouleComponentUpdate() 方法,当 props 或者 state 被改变, PureComponent 会对 props 和 state 做一次浅比较。另一方面,Component 不会将当前的 props 和 state 与下一次更新的进行比较。因此,每当调用shouldComponentUpdate 时,组件将默认重新渲染。

8. React 的 state 是什么

组件的 state 是一个对象,该对象包含一些可能在组件的生命周期内更改的信息。我们应该尽可能的使 state 尽可能简单,并减少有 state 组件的数量。 让我们创建一个带有消息状态的用户组件:

class User extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      message: 'Welcome to React world'
    }
  }

  render() {
    return (
      <div>
        <h1>{this.state.message}</h1>
      </div>
    )
  }
}

image.png

state 类似于 props,但它是私有的,并由组件完全控制。 也就是说除了拥有和设置它的组件之外,其他任何组件都无法访问它。

9. React 的 props 是什么

props 是组件的输入。它们是单个值或对象,包含一组值,这些值在创建时使用类似于 HTML-tag 属性的命名约定传递给组件。它们是从父组件传递到子组件的数据。

React 中的 props 的主要目的是提供以下组件功能:

  • 将自定义数据传递给组件。
  • 触发 state 改变。
  • 在组件的 render() 方法中,通过 this.props 使用。

举个例子,让我们创建一个元素并给一个 reactProp 属性:

<Element reactProp={'1'} />

然后,reactProp 将成为附加到 React 的本地 props 对象的属性,该对象最初已经存在于使用 React 创建的所有组件上。

props.reactProp

10. state 和 props 之间的不同是什么

prop 和 state 都是普通的 JS 对象。 尽管它们两者都具有影响渲染输出的信息,但它们在组件方面的功能不同。 props 被传递组件就像给函数传参,而 state 是在组件内进行管理类似于在函数中声明的变量。

11. 为什么不能直接更新 state

如果用下面的方式直接更新 State 并不会触发组件重新渲染

this.state.message = 'Hello world'

需要使用 setState 来对 state 对象进行更新,当 state 变化的时候,组件就会重新渲染

this.setState({ message: 'Hello World' })

12. 在 setState 中可以使用回调函数作为参数的目的是什么

回调函数将在 setState 执行完成和组件完成渲染后执行,由于 setState 是异步执行的,所以回调函数可以被用作在状态更新后的后续操作。

Note: 建议使用生命周期方法而不是使用这个回调函数。

setState(
  { 
    name: 'John' 
  }, 
  () => console.log('The name has updated and component re-rendered')
)

13. HTML 的事件绑定和 React 的事件绑定有什么不同

  • 在 HTML 中,绑定事件名称应该是全部是小写英文:
<button onclick='activateLasers()'>
  • 在 React 中是用小驼峰:
<button onClick={activateLasers}>
  • 在 HTML 中,你可以使用 return false 阻止默认行为:
<a href='#' onclick='console.log("The link was clicked."); return false;' />
  • 在 React 中必须使用 preventDefault
function handleClick(event) {
  event.preventDefault()
  console.log('The link was clicked.')
}
  • 在 HTML 中,你应该在函数名称后面加上 () 来使用函数,而在 React 中则不需要,同样参考前面的例子,HTML 中使用字符串,而在 React 中使用 {}

_

14. 怎样在 JSX 中绑定方法和事件处理

有 3 中方法来实现:

  • 绑定一个构造函数(Constructor): 在 JS 的类中,默认不绑定方法,用于构造 React 的类组件也同样如此,通常我们在构造函数中绑定它们
class Component extends React.Componenet {
  constructor(props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    // ...
  }
}
  • 使用箭头函数:你可以使用箭头函数定义公共方法或者直接在 JSX 对象里加入匿名函数,而不用使用 bind
handleClick = () => {
  console.log('this is:', this)
}

<button onClick={this.handleClick}>
  {'Click me'}
</button>

<button onClick={(event) => this.handleClick(event)}>
  {'Click me'}
</button>

15. 怎么给事件绑定或者回调函数传参

可以用箭头函数把包裹事件绑定然后传递参数

<button onClick={() => this.handleClick(id)} />

也可以使用bind

<button onClick={this.handleClick.bind(this, id)} />

或者可以使用高阶函数

handleClick = (id) => () => {
    console.log("Hello, your ticket number is", id)
};

<button onClick={this.handleClick(id)} />

15. 怎么给事件绑定或者回调函数传参


可以用箭头函数把包裹事件绑定然后传递参数

<button onClick={() => this.handleClick(id)} />


也可以使用bind

<button onClick={this.handleClick.bind(this, id)} />


或者可以使用高阶函数

handleClick = (id) => () => {
    console.log("Hello, your ticket number is", id)
};

<button onClick={this.handleClick(id)} />

16. 什么是 React 的合成事件


合成事件(SyntheticEvent) 是围绕浏览器原生事件的跨浏览器包装。 除了兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口。包括 stopPropagation() 和 preventDefault()

17. 什么是内联的条件表达式


你可以使用 if 语句或三元表达式来有条件的渲染表达式,还可以将任何表达式嵌入到 JSX 中,只需要用花括号封装起来,然后使用 JS逻辑运算符,如 &&:

<h1>Hello!</h1>
{
    messages.length > 0 && !isLogin?
      <h2>
          You have {messages.length} unread messages.
      </h2>
      :
      <h2>
          You don't have unread messages.
      </h2>
}

18. key 是什么?在数组元素中使用它有什么好处?


key 是在创建元素数组时应该包含的特殊字符串属性, key 用于帮助 React 识别哪些项已经更改、添加或删除。

我们经常使用我们数据中的 ID 来作为 key:

const todoItems = todos.map((todo) =>
  <li key={todo.id}>
    {todo.text}
  </li>
)


如果没有合适的 ID 的话也可以使用数组索引,不过建议是最后的选择:

const todoItems = todos.map((todo, index) =>
  <li key={index}>
    {todo.text}
  </li>
)


Notes:

  • 如果渲染元素的顺序可能更改,则不建议对使用索引。 这会对性能产生负面影响,并可能导致组件状态出现问题。
  • 如果每个数组中元素提取为单独的组件,则将 key 应用于组件而不是 li 标签。
  • 如果 key 没有作为列表项 prop,则控制台中将显示警告消息。

19. refs 有什么用


ref 用于返回对元素的引用。在大多数情况下应该避免使用它们,但是,如果需要直接访问 DOM 元素或组件的实例时,它们可能很有用。

20. 如何创建 refs


创建有多种方式:

  • 最新可使用 hooks 的 useRef 来创建应用:
const MyComponent = () => {
	const myRef = useRef()
  
  return <div ref={this.myRef} /> // use myRef.current
}
  • 这也是比较新的方法。 使用 React.createRef() 方法创建引用,并通过 ref 属性将其附加到 React元 素。 为了在整个组件中使用引用,只需将引用分配给构造函数中的实例属性:
class MyComponent extends React.Component {
  constructor(props) {
    super(props)
    this.myRef = React.createRef()
  }
  render() {
    return 
  }
}
  • 无论 React 版本如何,都可以使用 ref 回调方法。 例如,按以下方式访问搜索栏组件的输入元素:
class SearchBar extends Component {
   constructor(props) {
      super(props);
      this.txtSearch = null;
      this.state = { term: '' };
      this.setInputSearchRef = e => {
         this.txtSearch = e;
      }
   }
   onInputChange(event) {
      this.setState({ term: this.txtSearch.value });
   }
   render() {
      return (
         <input
            value={this.state.term}
            onChange={this.onInputChange.bind(this)}
            ref={this.setInputSearchRef} />
      );
   }
}


还可以使用函数组件中用闭包来使用 refs。注意:也可以使用内联引用回调,即使它不是一个推荐的方法

21. 什么是 forward refs


Ref forwarding 是一个新的特性,它允许一些组件接收它们接收到的 Ref,并将其进一步传递给子组件,简单来说将 ref 进行透传
_

const ButtonElement = React.forwardRef((props, ref) => (
  <button ref={ref} className="CustomButton">
    {props.children}
  </button>
));

// Create ref to the DOM button:
const ref = React.createRef();
<ButtonElement ref={ref}>{'Forward Ref'}</ButtonElement>

22. refs 和 findDOMNode() 应该选择那个


最好在使用 refs 而不使用 findDOMNode() API。 因为 findDOMNode() 将来会阻止React的某些改进

下面是个使用 findDOMNode 的例子:

class MyComponent extends Component {
  componentDidMount() {
    findDOMNode(this).scrollIntoView()
  }

  render() {
    return <div />
  }
}


而使用 refs 来代替:

class MyComponent extends Component {
  constructor(props){
    super(props);
    this.node = createRef();
  }
  componentDidMount() {
    this.node.current.scrollIntoView();
  }

  render() {
    return <div ref={this.node} />
  }
}

23. 为何字符串的 Refs 被遗弃了(待完善)


以前用过 React 的同学会知道一个一个较旧的 API,其中 ref 属性是一个字符串,例如ref = {'textInput'},并且 DOM 节点作为 this.refs.textInput 访问。 我们建议不要这样做,因为字符串引用存在以下问题,并且被认为是遗留问题。 所以在 React v16 中删除了字符串引用:

  • 强制 React 跟踪当前正在使用的组件,这是有问题的,因为它使 React 模块变成了有状态的,并因此在捆绑中复制 React 模块时导致奇怪的错误。
  • 不可组合 - 如果一个库在传递的子项上添加了 ref,则用户不能在其上添加 ref。而回调的 refs 完全可以组合。
  • 它们不适用 Flow 等静态分析。 Flow 无法猜测框架使字符串 ref 出现在 this.refs 上的效果及其类型(可能有所不同)。 回调的 refs 相比于静态分析更友好。
  • 不能像大多数人期望的那样使用「渲染回调」模式
class MyComponent extends Component {
  renderRow = (index) => {
    // 这无法生效,ref 会添加到 DataTable 上而不是 MyComponent
    return <input ref={'input-' + index} />;

    // 这是正确的方式
    return <input ref={input => this['input-' + index] = input} />;
  }

  render() {
    return <DataTable data={this.props.data} renderRow={this.renderRow} />
  }
}

24. 什么是虚拟 DOM


虚拟 DOM(VDOM)是真实 DOM 在内存中表示形式(其实是对象)。 UI 的表示形式保留在内存中,并与“真实” DOM 进行同步。 这是在调用渲染函数和在屏幕上显示元素的过程中做的一步。 这整个过程称为协调(Reconciliation)。

25. 虚拟 DOM 怎么运行的


虚拟 DOM 可以简单分为三步

  • 无论何时任何基础数据发生更改,整个 UI 都将以虚拟 DOM 表示形式重新计算呈现

image.png

  • 计算以前的 DOM 树和新的 DOM 树之间的差异

image.png

  • 一旦计算完成,实际的 DOM 将只更新需要改变的部分


image.png

26. 影子 DOM(Shadow DOM) 和虚拟 DOM 有何区别


译者补充:什么是 Shadow DOM

Shadow DOM 是一种浏览器原生支持技术,主要用于确定 web Component 中的变量和 CSS 的作用域。而虚拟DOM 是一个在浏览器 API 上面实现的方法。

27. 什么是 React Fiber


Fiber 是React v16 中核心算法的新协调(Reconciliation)引擎或重新实现,React Fiber 的目标是提高其在动画、布局、手势、暂停、中止或复用等方面的适用性,并为不同类型的更新分配优先级,以及新的???并发原语???

28. React Fiber 的主要目的是什么


React Fiber的目标是增强其在动画、布局和手势等方面的适用性。它的主要特性是增量渲染:能够将渲染工作分割成块,并将其分散到多个帧上

29. 什么是受控组件


受控组件是指控制表单中后续用户输入的输入元素的组件,每个状态的变化都用相应的处理函数。

如同下面的例子,我们对我们的输入全部变成大写,我们用 handleChange 来控制:

handleChange(event) {
  this.setState({value: event.target.value.toUpperCase()})
}

30. 什么是非受控组件


非受控组件是在内部存储其自身状态的组件,在需要时使用 ref 查询 DOM 以查找其当前值,和传统的 HTML 差不多

下面的 UserProfile 组件,用 ref 来获取 input 中的 name:

class UserProfile extends React.Component {
  constructor(props) {
    super(props)
    this.handleSubmit = this.handleSubmit.bind(this)
    this.input = React.createRef()
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.input.current.value)
    event.preventDefault()
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          {'Name:'}
          <input type="text" ref={this.input} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}


Note: 大多数情况下,建议使用受控组件来实现表单

31. cloneElement 和 createElement 之间有什么不同


JSX 会通过 React.createElemen() 函数进行转换后来创建用对象来表示的 React 元素最后渲染成 UI, 而cloneElement 用于克隆元素并传递新的 props。

32. React 中的状态提升是什么


当多个组件需要共享同一个的被更改数据时,建议将共享状态提升到最接近的公共父组件,也就是说,如果两个子组件共享来自其父组件的相同数据,则将 state 移到父组件,而不是在两个子组件都各自保持私有数据。、

33. 组件生命周期的不同阶段是什么


组件生命周期有三个不同的阶段

  • 加载(Mounting):这阶段组件已经准备好在 DOM 中挂载。此阶段包括下面这些方法进行组件初始化
    • constructor()
    • getDerivedStateFromProps()
    • render()
    • componentDidMount()
  • 更新(Updating):在此阶段,组件以两种方式进行更新,收到新的 props 和使用 setState() 或forceUpdate() 更新状态。该阶段包括:
    • getDerivedStateFromProps()
    • shouldComponentUpdate()
    • render()
    • getSnapshotBeforeUpdate()
    • componentDidUpdate()
  • 卸载(Unmounting):在最后一个阶段,组件不在需要,将其从 DOM 中卸载,这阶段包含:
    • componentWillUnmount()


有个值得了解的,在将更改应用于 DOM 时,React 内部也具有阶段性概念。 它们分开如下:

  • Render:组件将渲染时没有任何副作用。 这适用于 Pure 组件,在此阶段,React 可以暂停,中止或重新启动渲染。
  • Pre-commit: 在组件实际将更改应用于 DOM 之前,有一段时间允许React通过 getSnapshotBeforeUpdate 来读取 DOM。
  • Commit:  React 与 DOM 一起工作,并分别执行最终生命周期:componentDidMount 用于安装,componentDidUpdate 用于更新,componentWillUnmount 用于卸载。


React 生命周期在 16.3 后做过变动,React 16.3 生命周期图:

image.png


React 16.3 之前

image.png

34. React 生命周期方法有哪些


React 16.3+

  • getDerivedStateFromProps:在调用 render 之前立即调用,并在每个渲染器上调用。 对于需要派生状态的罕见用例,这是存在的。 如果您需要派生状态,则值得一读
  • componentDidMount:在第一次渲染之后执行,这里应该发生所有网络/ajax 请求,获取/操作 DOM 或 state 更新以及绑定监听事件。
  • shouldComponentUpdate:确定是否更新组件。 默认情况下,它返回 true。 如果您确定组件在状态或道具更新后不需要渲染,则可以返回 false 。 这是提高性能的好地方,因为如果组件收到新的道具,它可以防止重新渲染。
  • getSnapshotBeforeUpdate:在将呈现的输出提交到 DOM 之前立即执行。 由此返回的任何值都将传递到componentDidUpdate 中。 这对于从 DOM(即滚动位置)捕获信息很有用。
  • componentDidUpdate:通常用于更新 DOM 以响应属性或状态更改。 如果shouldComponentUpdate 返回false,则不会触发。
  • componentWillUnmount 它将用于取消任何传出的网络请求,或删除与该组件关联的所有监听事件。


React 16.3 之前:

  • componentWillMount:在渲染之前执行,用于根组件中的 App 级配置。
  • componentDidMount:同上
  • componentWillReceiveProps:在特定 props 更新以触发状态转换时执行。
  • shouldComponentUpdate:同上
  • componentWillUpdate:在应返回 true 的shouldComponentUpdate 确认 props 和 state 更改时,在重新渲染组件之前执行。
  • componentDidUpdate:同上
  • componentWillUnmount:同上

35. 什么是高阶组件

高阶组件(HOC) 其实就是接受一个组件作为参数并返回一个新组件作为参数的函数,是一种基于 React 的组合特性而形成的设计模式。

我们称它们为纯组件,因为它们可以接受任何动态提供的子组件,但它们不会修改或复制输入组件中的任何行为

const EnhancedComponent = higherOrderComponent(WrappedComponent)

HOC 的用例:

  • 代码,逻辑复用和抽象
  • 渲染劫持
  • 状态抽象和操作
  • props 操作

36. 如果创建高阶组件的 props 代理


可以使用 props 代理模式添加或编辑传递给组件的 props,如下所示:

function HOC(WrappedComponent) {
  return class Test extends Component {
    render() {
      const newProps = {
        title: 'New Header',
        footer: false,
        showFeatureX: false,
        showFeatureY: true
      }

      return <WrappedComponent {...this.props} {...newProps} />
    }
  }
}

37. 什么是 context


Context 提供了一种在组件树传递数据的方法,而不必在每个级别手动传递 props。例如,通过身份验证的用户、区域,语言设置首选项、UI主 题需要在应用程序中由许多组件访问。

const {Provider, Consumer} = React.createContext(defaultValue)

38. props 里面传递的 children 是啥


Children 是一个 props(this.prop.children),它允许将组件作为数据传递给其他组件,就像使用的任何其他 props 一样。放在组件之间的组件将作为子组件传递给该组件。

React API 中有许多方法可用于此 prop。 这些包括

  • React.Children.map
  • React.Children.forEach
  • React.Children.count
  • React.Children.only
  • React.Children.toArray


简单用法如下所示:

const MyDiv = React.createClass({
  render: function() {
    return <div>{this.props.children}</div>
  }
})

ReactDOM.render(
  <MyDiv>
    <span>{'Hello'}</span>
    <span>{'World'}</span>
  </MyDiv>,
  node
)

39. React 中怎么写注释


React / JSX 中的注释类似于JavaScript 多行注释,但是需要用花括号包裹:

单行注释:

<div>
  {/* Single-line comments(In vanilla JavaScript, the single-line comments are represented by double slash(//)) */}
  {`Welcome ${user}, let's play React`}
</div>


多行注释:

<div>
  {/* Multi-line comments for more than
   one line */}
  {`Welcome ${user}, let's play React`}
</div>

40. 在构造函数中 props 作为参数传入 super 是做什么


在调用 super() 方法之前,子类构造函数不能使用 this 引用。这同样适用于 ES6 子类。将 props 参数传递给super() 调用的主要原因是为了 this.props 能在子构造函数使用。

传递 props

class MyComponent extends React.Component {
  constructor(props) {
    super(props)

    console.log(this.props) // prints { name: 'John', age: 42 }
  }
}


不传递 props:

class MyComponent extends React.Component {
  constructor(props) {
    super()

    console.log(this.props) // prints undefined

    // but props parameter is still available
    console.log(props) // prints { name: 'John', age: 42 }
  }

  render() {
    // no difference outside constructor
    console.log(this.props) // prints { name: 'John', age: 42 }
  }
}

41. 什么是协调(reconciliation)


当一个组件的 props 或 state 发生改变,React 通过比较新返回的元素和之前呈现的元素来决定是否需要操作DOM 更新,当前后比较不相等,React 会更新 DOM。这个过程叫做协调(reconciliation)

42. 如果给一个变量设置 state


如果使用 ES6 或 Babel 来转换 JSX 代码,那么您可以使用计算出的属性名来完成这个操作。

handleInputChange(event) {
  this.setState({ [event.target.id]: event.target.value })
}

43. 每次组件渲染时调用函数的常见错误是什么


在将函数作为参数传递时,需要确保没有调用该函数:

render() {
  // Wrong: handleClick 被调用了,点击将不在触发函数
  return <button onClick={this.handleClick()}>{'Click Me'}</button>
}


正确的方式是:

render() {
  return <button onClick={this.handleClick}>{'Click Me'}</button>
}

44. 惰性函数是否支持有多个 export 的文件


React.lazy 目前只支持有默认导出的文件。如果要导入 exports 下的模块,则可以创建一个中间模块,将其重新导出为默认模块。 它还可以确保组件树保持正常,并且不会关联未使用的组件。 让我们以导出多个命名组件的组件文件为例

// MoreComponents.js
export const SomeComponent = /* ... */;
export const UnusedComponent = /* ... */;

在一个中间文件 IntermediateComponent.js 中再导出更多的 component.js 组件

// IntermediateComponent.js
export { SomeComponent as default } from "./MoreComponents.js";

现在就可以导入这个文件并使用 lazy

import React, { lazy } from 'react';
const SomeComponent = lazy(() => import("./IntermediateComponent.js"));

45. 为什么 React 使用 className 来代替 class 属性


class 是 JavaScript 中的关键字,而 JSX 是 JavaScript 的扩展,这就是为什么 React 使用 className 而不是class 的主要原因。传递一个字符串作为 className prop。

46. 什么是 fragments


这是 React 中的常见模式,用于组件返回多个元素, Fragments 使您可以将子项列表分组,而无需向 DOM 添加额外的节点.

render() {
  return (
    <React.Fragment>
      <ChildA />
      <ChildB />
      <ChildC />
    </React.Fragment>
  )
}

也可以使用语法糖

render() {
  return (
    <>
      <ChildA />
      <ChildB />
      <ChildC />
    </>
  )
}

47. 为什么用 fragments 比使用 DOM 包裹好?

  • 由于没有创建额外的 DOM 节点,Fragments 的速度更快,占用的内存更少。这对那些又大又深的 DOM 树有真正的好处。
  • 一些 CSS 机制(例如 Flexbox 和 CSS Grid)具有特殊的父子关系,并且在中间添加 div 使得难以保持所需的布局。
  • DOM 检查器比较简洁

48. 什么是 portals


Portal 是将子组件呈现到存在于父组件的 DOM 层次结构之外的 DOM 节点的推荐方法。

ReactDOM.createPortal(child, container)

第一个参数是任何可渲染的 React 子元素,例如元素,字符串或片段。 第二个参数是 DOM 元素。

中文官方文档介绍: zh-hans.reactjs.org/docs/portal…

49. 什么是无状态组件


如果组件行为独立于其状态,则它可以是无状态组件。你可以创建函数组件或者类组件来表示无状态组件。不过如果你不需要使用生命周期方法,建议使用函数组件。使用函数组件有很多好处,比如它们很容易编写,理解和测试,速度更快,而且完全可以避免使用 this 关键字。

50. 什么是有状态组件


如果组件的行为依赖于组件的状态,则可以将其称为有状态组件。这些有状态组件通常使用类组件,并且具有在构造函数中初始化 state。当然 React 16.8 后的版本可以在函数组件使用 hooks 生成有状态组件:

class App extends Component {
  constructor(props) {
    super(props)
    this.state = { count: 0 }
  }

  render() {
    // ...
  }
}

使用 hook

import React, {useState} from 'react';

const App = (props) => {
  const [count, setCount] = useState(0);

  return (
    // JSX
  )
}

51. 如何在 React 中对 props 进行验证


当应用程序在开发模式下运行时,React 会自动检查我们在组件上设置的所有 props,以确保它们的类型正确。如果类型不正确,React将在控制台中生成警告消息。由于性能影响,它在生产模式下被禁用。必须传入的 props 是用 isRequired 定义的.

预定义的类型如下:

  • PropTypes.number
  • PropTypes.string
  • PropTypes.array
  • PropTypes.object
  • PropTypes.func
  • PropTypes.node
  • PropTypes.element
  • PropTypes.bool
  • PropTypes.symbol
  • PropTypes.any


看下这个我们在 User 组件中使用 propTypes 的例子

import React from 'react'
import PropTypes from 'prop-types'

class User extends React.Component {
  static propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired
  }

  render() {
    return (
      <>
        <h1>{`Welcome, ${this.props.name}`}</h1>
        <h2>{`Age, ${this.props.age}`}</h2>
      </>
    )
  }
}

52. React 的优势是什么

  • 通过虚拟 DOM 来提高程序的性能
  • JSX 让代码易写和读
  • 支持本地渲染和服务端渲染
  • 易于和其他框架集成,因为它只是一个视图层
  • 易于编写集成和单元测试工具如 Jest

53. React 的局限性有哪些

  • React 只是一个视图层,不是一个完整框架
  • 对于初学者和新人有一定的学习曲线
  • 将 React 集成到传统的 MVC 框架中需要一些额外的配置
  • 内联模板和 JSX 增加了代码的复杂性
  • 太多较小的组件导致过度的工程设计或样板

54. React 16 中的错误边界是什么