那些年,本身沒回答上來的react面試題

一、React中元素與組件的區別

那個時候剛學react,不知道面試官說的元素是什麼,如今知道了,就是虛擬dom嘛。。。html

React 元素(React element)react

它是 React 中最小基本單位,咱們可使用 JSX 語法輕鬆地建立一個 React 元素:面試

const element = <div className="element">I'm element</div> 複製代碼

React 元素不是真實的 DOM 元素,它僅僅是 js 的普通對象(plain objects),因此也沒辦法直接調用 DOM 原生的 API。上面的 JSX 轉譯後的對象大概是這樣的:算法

{
    _context: Object,
    _owner: null,
    key: null,
    props: {
    className: 'element',
    children: 'I'm element' }, ref: null, type: "div" } 複製代碼

除了使用 JSX 語法,咱們還可使用 React.createElement() 和 React.cloneElement() 來構建 React 元素。設計模式

React 組件api

React 中有三種構建組件的方式。React.createClass()、ES6 class和無狀態函數。數組

一、React.createClass()瀏覽器

var Greeting = React.createClass({
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
});
複製代碼

二、ES6 classbash

class Greeting extends React.Component{
  render: function() {
    return <h1>Hello, {this.props.name}</h1>;
  }
};
複製代碼

三、無狀態函數數據結構

無狀態函數是使用函數構建的無狀態組件,無狀態組件傳入props和context兩個參數,它沒有state,除了render(),沒有其它生命週期方法。

function Greeting (props) {
  return <h1>Hello, {props.name}</h1>;
}
複製代碼

四、PureComponent

除了爲你提供了一個具備淺比較的shouldComponentUpdate方法,PureComponent和Component基本上徹底相同。

元素與組件的區別

組件是由元素構成的。元素數據結構是普通對象,而組件數據結構是類或純函數。

二、請說一說Forwarding Refs有什麼用

是父組件用來獲取子組件的dom元素的,爲何有這個API,緣由以下

// 例若有一個子組件和父組件,代碼以下
子組件爲:
class Child extends React.Component{
    constructor(props){
      super(props);
    }
    render(){
      return <input />
    }
 }

// 父組件中,ref:
class Father extends React.Component{
  constructor(props){
    super(props);
    this.myRef=React.createRef();
  }
  componentDidMount(){
    console.log(this.myRef.current);
  }
  render(){
    return <Child ref={this.myRef}/>
  }
}
複製代碼

此時父組件的this.myRef.current的值是Child組件,也就是一個對象,若是用了React.forwardRef,也就是以下

// 子組件
const Child = React.forwardRef((props, ref) => (
    <input ref={ref} />
));
// 父組件
class Father extends React.Component {
    constructor(props) {
        super(props);
        this.myRef = React.createRef();
    }
    componentDidMount() {
        console.log(this.myRef.current);
    }
    render() {
        return <Child ref={this.myRef} />
    }
}
複製代碼

此時父組件的this.myRef.current的值是input這個DOM元素

三、簡述一下virtual DOM 如何工做?

  • 當數據發生變化,好比setState時,會引發組件從新渲染,整個UI都會以virtual dom的形式從新渲染
  • 而後收集差別也就是diff新的virtual dom和老的virtual dom的差別
  • 最後把差別隊列裏的差別,好比增長節點、刪除節點、移動節點更新到真實的DOM上

四、你對Fiber架構瞭解嗎

注:答案參考司徒正美大神的文章

4.一、Fiber架構相對於之前的遞歸更新組件有有什麼優點

  • 緣由是遞歸更新組件會讓JS調用棧佔用很長時間。
  • 由於瀏覽器是單線程的,它將GUI渲染,事件處理,js執行等等放在了一塊兒,只有將它作完才能作下一件事,若是有足夠的時間,瀏覽器是會對咱們的代碼進行編譯優化(JIT)及進行熱代碼優化。
  • Fiber架構正是利用這個原理將組件渲染分段執行,提升這樣瀏覽器就有時間優化JS代碼與修正reflow!

4.一、既然你說Fiber是將組件分段渲染,那第一段渲染以後,怎麼知道下一段從哪一個組件開始渲染呢?

  • Fiber節點擁有return, child, sibling三個屬性,分別對應父節點, 第一個孩子, 它右邊的兄弟, 有了它們就足夠將一棵樹變成一個鏈表, 實現深度優化遍歷。

4.2 怎麼決定每次更新的數量

  • React16則是須要將虛擬DOM轉換爲Fiber節點,首先它規定一個時間段內,而後在這個時間段能轉換多少個FiberNode,就更新多少個。
  • 所以咱們須要將咱們的更新邏輯分紅兩個階段,第一個階段是將虛擬DOM轉換成Fiber, Fiber轉換成組件實例或真實DOM(不插入DOM樹,插入DOM樹會reflow)。Fiber轉換成後二者明顯會耗時,須要計算還剩下多少時間。
  • 好比,能夠記錄開始更新視圖的時間var now = new Date - 0,假如咱們更新試圖自定義須要100毫秒,那麼定義結束時間是var deadline = new Date + 100 ,因此每次更新一部分視圖,就去拿當前時間new Date<deadline作判斷,若是沒有超過deadline就更新視圖,超過了,就進入下一個更新階段

4.3 如何調度時間才能保證流暢

  • 使用瀏覽器自帶的api - requestIdleCallback,
  • 它的第一個參數是一個回調,回調有一個參數對象,對象有一個timeRemaining方法,就至關於new Date - deadline,而且它是一個高精度數據, 比毫秒更準確
  • 這個由於瀏覽器兼容性問題,react團隊本身實現了requestIdleCallback

4.4 fiber帶來的新的生命週期是什麼

建立時

  • constructor ->
  • getDerivedStateFromProps(參數nextProps, prevState,注意裏面this不指向組件的實例)->
  • render ->
  • componentDidMount

更新時

  • getDerivedStateFromProps(這個時props更新才調用,setState時不調用這個生命週期, 參數nextProps, prevState) ->
  • shouldComponentUpdate(setState時調用參數nextProps, nextState)->
  • render->
  • getSnapsshotBeforeUpdate(替換 componentWillUpdate)
  • componentDidUpdate(參數prevProps, prevState, snapshot)

五、什麼是受控組件和非受控組件

  • 受狀態控制的組件,必需要有onChange方法,不然不能使用 受控組件能夠賦予默認值(官方推薦使用 受控組件) 實現雙向數據綁定
class Input extends Component{
    constructor(){
        super();
        this.state = {val:'100'}
    }
    handleChange = (e) =>{ //e是事件源
        let val = e.target.value;
        this.setState({val});
    };
    render(){
        return (<div>
            <input type="text" value={this.state.val} onChange={this.handleChange}/>
            {this.state.val}
        </div>)
    }
}
複製代碼
  • 非受控也就意味着我能夠不須要設置它的state屬性,而經過ref來操做真實的DOM
class Sum extends Component{
    constructor(){
        super();
        this.state =  {result:''}
    }
    //經過ref設置的屬性 能夠經過this.refs獲取到對應的dom元素
    handleChange = () =>{
        let result = this.refs.a.value + this.b.value;
        this.setState({result});
    };
    render(){
        return (
            <div onChange={this.handleChange}>
                <input type="number" ref="a"/>
                {/*x表明的真實的dom,把元素掛載在了當前實例上*/}
                <input type="number" ref={(x)=>{
                    this.b = x;
                }}/>
                {this.state.result}
            </div>
        )
    }
}
複製代碼

六、什麼是狀態提高

使用 react 常常會遇到幾個組件須要共用狀態數據的狀況。這種狀況下,咱們最好將這部分共享的狀態提高至他們最近的父組件當中進行管理。咱們來看一下具體如何操做吧。

import React from 'react'
class Child_1 extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return (
            <div>
                <h1>{this.props.value+2}</h1>
            </div> 
        )
    }
}
class Child_2 extends React.Component{
    constructor(props){
        super(props)
    }
    render(){
        return (
            <div>
                <h1>{this.props.value+1}</h1>
            </div> 
        )
    }
}
class Three extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            txt:"牛逼"
        }
        this.handleChange = this.handleChange.bind(this)
    }
    handleChange(e){
        this.setState({
            txt:e.target.value
        })
    }
    render(){
       return (
            <div>
                <input type="text" value={this.state.txt} onChange={this.handleChange}/>
                <p>{this.state.txt}</p>
                <Child_1 value={this.state.txt}/>
                <Child_2 value={this.state.txt}/>
            </div>
       )
    }
}
export default Three
複製代碼

七、什麼是高階組件

高階組件不是組件,是 加強函數,能夠輸入一個元組件,返回出一個新的加強組件

  • 屬性代理 (Props Proxy) 在我看來屬性代理就是提取公共的數據和方法到父組件,子組件只負責渲染數據,至關於設計模式裏的模板模式,這樣組件的重用性就更高了
function proxyHoc(WrappedComponent) {
	return class extends React.Component {
		render() {
			const newProps = {
				count: 1
			}
			return <WrappedComponent {...this.props} {...newProps} />
		}
	}
}
複製代碼
  • 反向繼承
const MyContainer = (WrappedComponent)=>{
    return class extends WrappedComponent {
        render(){
            return super.render();
        }
    }
}
複製代碼

八、什麼是上下文Context

Context 經過組件樹提供了一個傳遞數據的方法,從而避免了在每個層級手動的傳遞 props 屬性。

  • 用法:在父組件上定義getChildContext方法,返回一個對象,而後它的子組件就能夠經過this.context屬性來獲取
import React,{Component} from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';
class Header extends Component{
    render() {
        return (
            <div>
                <Title/>
            </div>
        )
    }
}
class Title extends Component{
    static contextTypes={
        color:PropTypes.string
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Title
            </div>
        )
    }
}
class Main extends Component{
    render() {
        return (
            <div>
                <Content>
                </Content>
            </div>
        )
    }
}
class Content extends Component{
    static contextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    render() {
        return (
            <div style={{color:this.context.color}}>
                Content
                <button onClick={()=>this.context.changeColor('green')}>綠色</button>
                <button onClick={()=>this.context.changeColor('orange')}>橙色</button>
            </div>
        )
    }
}
class Page extends Component{
    constructor() {
        super();
        this.state={color:'red'};
    }
    static childContextTypes={
        color: PropTypes.string,
        changeColor:PropTypes.func
    }
    getChildContext() {
        return {
            color: this.state.color,
            changeColor:(color)=>{
                this.setState({color})
            }
        }
    }
    render() {
        return (
            <div>
                <Header/>
                <Main/>
            </div>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));
複製代碼

九、react中的Portal是什麼?

Portals 提供了一種很好的將子節點渲染到父組件之外的 DOM 節點的方式。

ReactDOM.createPortal(child, container)
複製代碼

第一個參數(child)是任何可渲染的 React 子元素,例如一個元素,字符串或碎片。第二個參數(container)則是一個 DOM 元素。

十、react16的錯誤邊界(Error Boundaries)是什麼

部分 UI 中的 JavaScript 錯誤不該該破壞整個應用程序。 爲了解決 React 用戶的這個問題,React 16引入了一個 「錯誤邊界(Error Boundaries)」 的新概念。

import React from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends React.Component{
    constructor(props) {
        super(props);
        this.state={hasError:false};
    }
    componentDidCatch(err,info) {
        this.setState({hasError: true});
    }
    render() {
        if (this.state.hasError) {
            return <h1>Something Went Wrong</h1>
        }
        return this.props.children;
    }
}

class Page extends React.Component{
    render() {
        return (
            <ErrorBoundary>
                <Clock/>
            </ErrorBoundary>
        )
    }
}
class Clock extends React.Component{
    render() {
        return (
            <div>hello{null.toString()}</div>
        )
    }
}

ReactDOM.render(<Page/>,document.querySelector('#root'));
複製代碼

十一、如何在React中使用innerHTML

增長dangerouslySetInnerHTML屬性,而且傳入對象的屬性名叫_html

function Component(props){
    return <div dangerouslySetInnerHTML={{_html:'<span>你好</span>'}}>
            </div>
}
複製代碼

十二、react16版本的reconciliation階段和commit階段是什麼

  • reconciliation階段包含的主要工做是對current tree 和 new tree 作diff計算,找出變化部分。進行遍歷、對比等是能夠中斷,歇一下子接着再來。
  • commit階段是對上一階段獲取到的變化部分應用到真實的DOM樹中,是一系列的DOM操做。不只要維護更復雜的DOM狀態,並且中斷後再繼續,會對用戶體驗形成影響。在廣泛的應用場景下,此階段的耗時比diff計算等耗時相對短。

1三、請簡單談一下react的事件機制

  • 當用戶在爲onClick添加函數時,React並無將Click時間綁定在DOM上面。
  • 而是在document處監聽全部支持的事件,當事件發生並冒泡至document處時,React將事件內容封裝交給中間層SyntheticEvent(負責全部事件合成)
  • 因此當事件觸發的時候,對使用統一的分發函數dispatchEvent將指定函數執行。

1四、爲何列表循環渲染的key最好不要用index

舉例說明

變化前數組的值是[1,2,3,4],key就是對應的下標:0,1,2,3
變化後數組的值是[4,3,2,1],key對應的下標也是:0,1,2,3
複製代碼
  • 那麼diff算法在變化前的數組找到key =0的值是1,在變化後數組裏找到的key=0的值是4
  • 由於子元素不同就從新刪除並更新
  • 可是若是加了惟一的key,以下
變化前數組的值是[1,2,3,4],key就是對應的下標:id0,id1,id2,id3
變化後數組的值是[4,3,2,1],key對應的下標也是:id3,id2,id1,id0
複製代碼
  • 那麼diff算法在變化前的數組找到key =id0的值是1,在變化後數組裏找到的key=id0的值也是1
  • 由於子元素相同,就不刪除並更新,只作移動操做,這就提高了性能
相關文章
相關標籤/搜索