React 经常使用面试题目与分析翻译自React Interview Questions,从属于笔者的Web 前端入门与工程实践,更多前端思考借鉴2016-个人前端之路:工具化与工程化前端
在代码中调用setState
函数以后,React 会将传入的参数对象与组件当前的状态合并,而后触发所谓的调和过程(Reconciliation)。通过调和过程,React 会以相对高效的方式根据新的状态构建 React 元素树而且着手从新渲染整个UI界面。在 React 获得元素树以后,React 会自动计算出新的树与老树的节点差别,而后根据差别对界面进行最小化重渲染。在差别计算算法中,React 可以相对精确地知道哪些位置发生了改变以及应该如何改变,这就保证了按需更新,而不是所有从新渲染。react
简单而言,React Element 是描述屏幕上所见内容的数据结构,是对于 UI 的对象表述。典型的 React Element 就是利用 JSX 构建的声明式代码片而后被转化为createElement
的调用组合。而 React Component 则是能够接收参数输入而且返回某个 React Element 的函数或者类。更多介绍能够参考React Elements vs React Components。git
在组件须要包含内部状态或者使用到生命周期函数的时候使用 Class Component ,不然使用函数式组件。github
Refs 是 React 提供给咱们的安全访问 DOM 元素或者某个组件实例的句柄。咱们能够为元素添加ref
属性而后在回调函数中接受该元素在 DOM 树中的句柄,该值会做为回调函数的第一个参数返回:面试
class CustomForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
上述代码中的input
域包含了一个ref
属性,该属性声明的回调函数会接收input
对应的 DOM 元素,咱们将其绑定到this
指针以便在其余的类函数中使用。另外值得一提的是,refs 并非类组件的专属,函数式组件一样可以利用闭包暂存其值:算法
function CustomForm ({handleSubmit}) { let inputElement return ( <form onSubmit={() => handleSubmit(inputElement.value)}> <input type='text' ref={(input) => inputElement = input} /> <button type='submit'>Submit</button> </form> ) }
Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。数组
render () { return ( <ul> {this.state.todoItems.map(({task, uid}) => { return <li key={uid}>{task}</li> })} </ul> ) }
在开发过程当中,咱们须要保证某个元素的 key 在其同级元素中具备惟一性。在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近建立的仍是被移动而来的元素,从而减小没必要要的元素重渲染。此外,React 还须要借助 Key 值来判断元素与本地状态的关联关系,所以咱们毫不可忽视转换函数中 Key 的重要性。promise
Twitter
元素,那么它相关的类定义是啥样子的?<Twitter username='tylermcginnis33'> {(user) => user === null ? <Loading /> : <Badge info={user} />} </Twitter>
import React, { Component, PropTypes } from 'react' import fetchUser from 'twitter' // fetchUser take in a username returns a promise // which will resolve with that username's data. class Twitter extends Component { // finish this }
若是你还不熟悉回调渲染模式(Render Callback Pattern),这个代码可能看起来有点怪。这种模式中,组件会接收某个函数做为其子组件,而后在渲染函数中以props.children
进行调用:浏览器
import React, { Component, PropTypes } from 'react' import fetchUser from 'twitter' class Twitter extends Component { state = { user: null, } static propTypes = { username: PropTypes.string.isRequired, } componentDidMount () { fetchUser(this.props.username) .then((user) => this.setState({user})) } render () { return this.props.children(this.state.user) } }
这种模式的优点在于将父组件与子组件解耦和,父组件能够直接访问子组件的内部状态而不须要再经过Props传递,这样父组件可以更为方便地控制子组件展现的UI界面。譬如产品经理让咱们将本来展现的Badge
替换为Profile
,咱们能够轻易地修改下回调函数便可:安全
<Twitter username='tylermcginnis33'> {(user) => user === null ? <Loading /> : <Profile info={user} />} </Twitter>
React 的核心组成之一就是可以维持内部状态的自治组件,不过当咱们引入原生的HTML表单元素时(input,select,textarea 等),咱们是否应该将全部的数据托管到 React 组件中仍是将其仍然保留在 DOM 元素中呢?这个问题的答案就是受控组件与非受控组件的定义分割。受控组件(Controlled Component)代指那些交由 React 控制而且全部的表单数据统一存放的组件。譬以下面这段代码中username
变量值并无存放到DOM元素中,而是存放在组件状态数据中。任什么时候候咱们须要改变username
变量值时,咱们应当调用setState
函数进行修改。
class ControlledForm extends Component { state = { username: '' } updateUsername = (e) => { this.setState({ username: e.target.value, }) } handleSubmit = () => {} render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' value={this.state.username} onChange={this.updateUsername} /> <button type='submit'>Submit</button> </form> ) } }
而非受控组件(Uncontrolled Component)则是由DOM存放表单数据,并不是存放在 React 组件中。咱们可使用 refs 来操控DOM元素:
class UnControlledForm extends Component { handleSubmit = () => { console.log("Input Value: ", this.input.value) } render () { return ( <form onSubmit={this.handleSubmit}> <input type='text' ref={(input) => this.input = input} /> <button type='submit'>Submit</button> </form> ) } }
居然非受控组件看上去更好实现,咱们能够直接从 DOM 中抓取数据,而不须要添加额外的代码。不过实际开发中咱们并不提倡使用非受控组件,由于实际状况下咱们须要更多的考虑表单验证、选择性的开启或者关闭按钮点击、强制输入格式等功能支持,而此时咱们将数据托管到 React 中有助于咱们更好地以声明式的方式完成这些功能。引入 React 或者其余 MVVM 框架最初的缘由就是为了将咱们从繁重的直接操做 DOM 中解放出来。
咱们应当将AJAX 请求放到 componentDidMount 函数中执行,主要缘由有下:
React 下一代调和算法 Fiber 会经过开始或中止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 这个生命周期函数的调用次数会变得不肯定,React 可能会屡次频繁调用 componentWillMount。若是咱们将 AJAX 请求放到 componentWillMount 函数中,那么显而易见其会被触发屡次,天然也就不是好的选择。
若是咱们将 AJAX 请求放置在生命周期的其余函数中,咱们并不能保证请求仅在组件挂载完毕后才会要求响应。若是咱们的数据请求在组件挂载以前就完成,而且调用了setState
函数将数据添加到组件状态中,对于未挂载的组件则会报错。而在 componentDidMount 函数中进行 AJAX 请求则能有效避免这个问题。
shouldComponentUpdate 容许咱们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值可以帮咱们避免没必要要的更新。
一般状况下咱们会使用 Webpack 的 DefinePlugin 方法来将 NODE_ENV 变量值设置为 production。编译版本中 React 会忽略 propType 验证以及其余的告警信息,同时还会下降代码库的大小,React 使用了 Uglify 插件来移除生产环境下没必要要的注释等信息。
props.children
并不必定是数组类型,譬以下面这个元素:
<Parent> <h1>Welcome.</h1> </Parent>
若是咱们使用props.children.map
函数来遍历时会受到异常提示,由于在这种状况下props.children
是对象(object)而不是数组(array)。React 当且仅当超过一个子元素的状况下会将props.children
设置为数组,就像下面这个代码片:
<Parent> <h1>Welcome.</h1> <h2>props.children will now be an array</h2> </Parent>
这也就是咱们优先选择使用React.Children.map
函数的缘由,其已经将props.children
不一样类型的状况考虑在内了。
为了解决跨浏览器兼容性问题,React 会将浏览器原生事件(Browser Native Event)封装为合成事件(SyntheticEvent)传入设置的事件处理器中。这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差别,保证了行为的一致性。另外有意思的是,React 并无直接将事件附着到子元素上,而是以单一事件监听器的方式将全部的事件发送到顶层进行处理。这样 React 在更新 DOM 的时候就不须要考虑如何去处理附着在 DOM 上的事件监听器,最终达到优化性能的目的。
createElement 函数是 JSX 编译以后使用的建立 React Element 的函数,而 cloneElement 则是用于复制某个元素并传入新的 Props。
该函数会在setState
函数调用完成而且组件开始重渲染的时候被调用,咱们能够用该函数来监听渲染是否完成:
this.setState( { username: 'tylermcginnis33' }, () => console.log('setState has finished and the component has re-rendered.') )
this.setState((prevState, props) => { return { streak: prevState.streak + props.count } })
这段代码没啥问题,不过只是不太经常使用罢了,详细能够参考React中setState同步更新策略