关于 react 那些小知识点儿

react.jpg
说在前面

关于 react 的总结过去半年就一直碎碎念着要搞起来,各(wo)种(tai)原(lan)因(le)。心心念的东西终于要重新拿了起来了。希望这个总结归纳能对你的日常开发或者跳槽面试有帮助哪怕只有那么一点点,反正对我帮助是挺大,温故而知新嘛!

废话说了一堆, 这个总结可能大概也许会以问答的形式总结
希望你能各个击破,像闯关卡一样一个一个过!开始吧 !Let's go!

【1】react component有几种写法?分别是什么?

① 函数式定义的无状态组件(Stateless Functional)

  • 性能更高效、代码更简洁
  • 没有 state,也就是无状态
  • 不需要管理/维护 组件的生命周期
  • 纯函数,相同的 props 会得到同样的UI渲染结果
    function List (props) {
        return <div>我是一个函数式定义的react组件</div>
    }

ES5方式 React.createClass 定义的组件(该方式已经被废弃,推荐使用①和③)
③ ES6 方式定义的组件(Class Components)

    class List extends React.Component {
        render() {
            return <div>我是一个es6方式定义的react组件</div>
        }
    }

官方文档写的还是颇具神秘感的,先告诉我们①和③方式在 UI 渲染效果是一毛一样的,但是'Classes have some additional features...' Class 这种方式比 Functional 这种方式多了些不一样的地方。那么问题来了。多了哪些不一样的呢? 不一样的地方你可能也发现了,有无 state有无生命周期...等

【2】那什么时候该用 Stateless Functional 什么时候用 Class 呢?

推荐使用 Functional,能用 Functional 就用 Functional,就酱。
多说一句。
Class 是用来创建包含状态生命周期用户交互的复杂组件,而当组件只是用来纯展示或者 props 传递渲染时(展示性),二话不说请用 Stateless Functional 来快速创建组件。

【3】无状态组件(Stateless Functional)有哪些优缺点
  • 优点
  1. 语法/代码更加简洁
  2. 占用内存小(没有 props 等属性), 首次 render 性能更好
  3. 不需要管理/维护组件的生命周期
  4. 纯函数,相同的 props 会得到同样的 UI 渲染结果
  5. 单元测试更容易进行。因为逻辑都被移出了 view 层,所以单元测试时不需要渲染任何东西,可以专注于单个逻辑。
  • 缺点
  1. 无生命周期函数。对于一个函数而言应该是谈不上生命周期。当然了,我们其实可以使用高阶组件去实现生命周期。
  2. 没有 this。在Stateless中 this 是 undefined 。
【4】React.Component 绑定方法的几种方法?
    //第一种方法:构造函数中绑定
    
    class List extends React.Component {
        constructor(props) {
            super(props)
            this.onClickList = this.onClickList.bind(this)
        }
        
        onClickList() {
            console.log('我被点了')
        }
        
        render() {
            return <div onClick={this.onClickList}>点我点我点我</div>
        }
        
    }
    
    //第二种方法: 在render()行内事件使用bind来绑定
    
    class List extends React.Component {
        
        onClickList() {
            console.log('我被点了')
        }
        
        render() {
            return <div onClick={this.onClickList.bind(this)}>点我点我点我</div>
        }
        
    }
    
    //第三种方法: 使用箭头函数 => 
    
    class List extends React.Component {
        
        onClickList = () => {
            console.log('我被点了')
        }
        
        render() {
            return <div onClick={this.onClickList}>点我点我点我</div>
        }
        
    }
    
    //第四种,当然,你要在render()行内使用箭头函数也行
    
    class List extends React.Component {
        
        onClickList() {
            console.log('我被点了')
        }
        
        render() {
            return <div onClick={() => this.onClickList()}>点我点我点我</div>
        }
        
    }

我日常开发都比较喜欢用箭头函数的方法,代码量比第一种少😊。当然,官方说在 render 中创建函数(第二,和第四种)可能会有性能问题。但往往需要传递参数或者回调时,都得用到。例如:

    <button onClick={this.handleClick.bind(this, id)} />
    <button onClick={() => this.handleClick(id)} />
【5】智能组件 vs 木偶组件 ?(容器组件 vs 展示组件)

Smart 组件 和 Dumb 组件对于开发过 react 项目的朋友来说应该不陌生了。

Dumb 组件,听名字你就知道这种组件很傻很木,因为木偶组件只关心一件事情就是 —— 根据 props 进行渲染。
Smart 组件就很聪明,它专门做数据相关的逻辑,和各路数据打交道,ajax获取数据,定义好数据操作的相关函数,然后将这些数据、函数直接传递给具体实现的组件(Dumb 组件)即可。所以根据是否需要高度的复用性,把组件划分为 Dumb 和 Smart 组件。

小提示1:Smart 组件复用性不强甚至不需要复用,Dumb 组件往往是复用性强的,但是Dumb 组件对于 Smart 组件的带入性不要太强,因为带入太多逻辑会导致复用性降低。二者选择,设计组件时需要斟酌一下。

小提示2:Dumb 组件 的子组件也应该是 Dumb 组件。

小提示3:redux store 相关的应该和 Smart 组件连接起来 。

关于React生命周期

关于生命周期,面试的时候总喜欢问点儿react生命周期相关的,而且想要了解别人写的 react 代码,深刻理解 react 生命周期也是很重要的。先不要往下看,闭上眼睛想想看你所了解的 react 生命周期有哪些?

...

...

...

...

...

...

liftcycle.jpg

ok 你应该想完了哈。是不是大概有下面这么一个流程?(忽略图片的渣像素😅图片是经典的组件挂载图来源于网络)

react 组件的生命周期方法都可以被分割成四个阶段:初始化、挂载阶段(Mounting)、更新阶段(Updating)、卸载阶段(Unmounting)。

接下来就让我们去看看生命周期都有哪些小知识点。

【6】Mounting -- 下面这些方法将会在 component 实例被创建和插入到DOM后调用。
  • constructor()
  • componentWillMount()
  • render()
  • componentDidMount()
【7】Updating -- props 或者 state 的变化都会导致更新。下面这些方法会在 component 重新渲染时调用。
  • componentWillReceiveProps()
  • shouldComponentUpdate()
  • componentWillUpdate()
  • render()
  • componentDidUpdate()
【8】Unmounting -- 该方法将会在 component 从DOM中移除时调用。
  • componentWillUnmount()

接下来简单的介绍一下几个生命周期。

【9】1. componentWillMount

componentWillMount() 是在组件挂载(mount)之前被调用.
componentWillMount()是唯一一个在服务器端渲染(ssr)调用的生命周期钩子

关于 setState 在 componentWillMount 使用:可以使用。因为它是 render 方法之前被调用,因此 setState 也不会导致重绘(re-render)

【10】2. componentDidMount

componentDidMount() 在组件挂载之后立即执行

在这个钩子里合适:

  • ajax 请求
  • 初始化DOM节点的操作
  • 设置计时器 setTimeout 或者 setInterval (温馨提示,别忘了在 componentWillUnmount 关闭这些计时器)

关于 setState 在 componentDidMount 使用: 可以使用。但是经常导致性能问题。当然非要在 render 前拿到 DOM 节点的大小和位置,是可以用的。

插曲。面试题:ajax 请求应该在哪个生命周期?为什么?

【11】3. componentWillReceiveProps(nextProps)

componentWillReceiveProps 将会在已挂载组件(mounted component)接收到新的 props 之前调用。所以初始化 props 的mount不会触发这个函数。直接 setState不会触发这个函数。

在这个钩子里合适:

  • 更新 state 的值(比如重置)
  • 比较 this.props 和 nextProps

特别特别特别要注意的是,当父组件导致该组件 re-render 时,即便 props 没有发生任何的改变,react 也有可能执行该钩子函数。所以呢,所以就是如果你想要真正处理 props 的变化,要记得比较当前 props 和 nextProps.

关于setState在componentWillReceiveProps使用: 可以使用

【12】4. shouldComponentUpdate(nextProps, nextState)

当改变state 或者 props 并且是在render之前会调用shouldComponentUpdate,说白了就是该钩子函数用于告诉 React 组件是否需要重新渲染。

shouldComponentUpdate 默认return true,如果return false componentWillUpdaterendercomponentDidUpdate都将不会被调用。千万记住一点, 当return false时,当他们的 state 发生改变时,并不会阻止子组件(child component)进行重新渲染。

shouldComponentUpdate在两种情况下不会被调用:

  • 组件初始化
  • 使用forceUpdate的情况

大家应该都是 shouldComponentUpdate 还有一个知识点就是和 react 组件性能优化相关的。是的。你可以this.state 和 nextState、this.props 和 nextProps 做比较来决定出 return false 并告诉 react 可以不更新该组件。如果做的只是一些浅层的数据比较完全可以用 PureComponent 来代替(深层的嵌套数据PureComponent也无能为力)

react 不建议在 shouldComponentUpdate 做深层的对比或者用 JSON.stringify(),因为这样反而损害到性能。

【13】5. componentWillUpdate(nextProps, nextState)

state 或者 props 更新后 re-render 之前调用。

注意:不要在componentWillUpdate 使用 this.setState, 或者 redux 分发一个action(dispatch a Redux action),因为在 componentWillUpdate 之前会触发组件的更新。 如果非要在做以上操作的话,可以在componentWillReceiveProps 哦

【14】6. componentDidUpdate(prevProps, prevState)

在组件更新之后马上调用 componentDidUpdate。

在这个钩子函数中你可以:

  • 操作 DOM
  • 发起网络请求
【15】7. componentWillUnmount

在组件卸载(unmounted)和销毁(destroyed)前调用。

在componentWillUnmount你可以执行任何需要清除的方法。比如:

  • 清除计时器
  • 断开网络请求
  • 解绑dom事件
  • 等等
【16】生命周期table
生命周期 是否可以调用this.setState 初始化是否执行
componentWillMount 可以
componentDidMount 可以
componentWillReceiveProps 可以
shouldComponentUpdate 不可以
componentWillUpdate 不可以
componentDidUpdate 可以
componentWillUnmount 不可以

特别特别特别注意:
①componentWillMount 和 componentWillReceiveProps 调用 setState 不会重复渲染(re-render)
②componentDidUpdate,不能直接 this.setState, 不然会溢出栈。需要对 prevProps 与 this.props 和 prevState 和 this.state 做一个判断再执行 this.setState。就类似while循环不能陷入死循环。

好吧。长篇大论了一番 react 的生命周期。不为什么,就因为它非常的重要。不管是对你的面试或者日常开发或者阅读理解别人的代码都是非常重要。哪个阶段会触发哪个生命周期,哪个能干什么哪个不能干什么,哪个更合适哪个不合适。来!干了它,咱们再继续往下看!

......
.....
....
...
..
.
感谢你能看到这里,咱们继续往下凿....
.
..
...
....
.....
......

【17】props 和 state 的区别
  1. "props"是别人的, props实现组件间的状态传递,props从父组件到子组建的数据传递;"state"是自己的,state只能定义在组件内部,定义组件的自己的状态。
  2. props 是不可变的; state 可以通过this.setState改变
【18】props vs state
? props state
可以从父组件获得初始值吗? Yes Yes
可以被父组件改变吗? Yes No
内部(当前)组件可以设置默认值吗? Yes Yes
可以改变内部(当前)组件吗? No Yes
可以为子组件设置初始值吗? Yes Yes
可以改变子组件吗? Yes No
【19】jsx是什么?

刚接触 react 的童鞋,看到 jsx,第一反应就是“丑”。说实在的一开始写 jsx 我也是拒绝的,但是没想到 jsx 其语法和背后的逻辑让构建react组件变得极其简单。
那 jsx 到底是什么呢?jsx 是一个看起来很像 XML 的 JavaScript 语法扩展。说白了 jsx 并不是什么高深的技术,可以说只是一个比较高级但很直观的语法糖。它非常有用,却不是一个必需品,没有 jsx 的 React 也可以正常工作:只要你乐意用 JavaScript 代码去创建这些虚拟 DOM 元素(但是真的超级麻烦)。

jsx优点:

  • 执行更快,因为它在编译为 JavaScript 代码后进行了优化
  • 它是类型安全的,在编译过程中就能发现错误
  • 编写模板更加简单快速
  • 更加直观,可读性高

来看看以下代码:
1.当我们用HTML描述一个按钮的时候,你会发现一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素

    <div id="btn-wrap">
        <button class="btn">click</button>
    </div>

2.我们如果用js描述,可以通过JSON对象,且依然包括元素的标签名、属性,子元素

    {
        type: 'div',
        props: { id: 'btn-wrap' },
        children: {
            type: 'button',
            props: { className: 'btn' },
            children: 'click'
        }
    }

仔细观察,你会发现HTML和js描述一个按钮他们所对应的结构简直是一毛一样的,就是说一个html构建的UI界面我们完全可以用js来描述。你会发现HTML书写一个按钮远比js书写方式来得苏胡,而且结构更加清晰。但是如果 你坚决要用js来写我也不会反对的。来!先写个div十层嵌套试试?

react提供jsx语法糖,将html语法直接加入到JavaScript代码中去,再通过编译器(babel)转化为JavaScript后由浏览器执行。

我们修改src/index.js的代码如下

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class HappyReact extends Component {
  render() {
    return (
        <div>
            <h1 id="title">happy react !</h1>
        </div>
    )
  }
}

ReactDOM.render(<HappyReact />, document.getElementById('root'));

这时候你会看到页面浏览器自动刷新了且页面显示了'happy react !'字样。

如果以上代码经过编译会变成:

import React, { Component } from 'react';
import ReactDOM from 'react-dom';

class HappyReact extends Component {
  render() {
    return (
      React.createElement(
        'div',
        null,
        React.createElement(
            'h1',
            { id: 'title' },
            'happy react !'
        )
      )
    )
  }
}

ReactDOM.render(
    React.createElement(
        HappyReact,
        null
    ),
    document.getElementById('root')
);

编译前后两段代码渲染结果是一样的,你一定发现了jsx的代码更加直观便于维护了吧!

虽然你看到的html写在了js代码中,但是你永远要记住"jsx最终其实就是JavaScript对象"。react通这个对象来创建或者更新虚拟元素最终来管理virtual DOM(虚拟DOM)

jsx对象元素可以理解为和真实元素一一对应的,它的创建、更新、删除都是在内存中完成的。并不会直接渲染到真实DOM中去,整个react应用程序唯一操作到DOM就是:

    ReactDOM.render(<HappyReact />, document.getElementById('root'));
【20】大概知道jsx是什么了。我们是得花点儿时间学习/了解/回忆一下jsx的写法。
  1. render函数只能return一个根节点,只允许被一个标签包裹
  2. Component 命名首字大写,HTML 标签用小写
  3. 如果不存在子节点,可以使用自闭合 <div />
  4. jsx的注释 {/* */}
  5. JavaScript 属性表达式,属性值用 {}
  6. 三元表达式
  7. 数组递归(渲染列表) map
  8. 两个特殊属性 class, for. 因为class, for在JavaScript中这个两个单词都是关键词。因此需要做一手转换。其他属性可以像写html一样添加上去。
  9. jsx书写样式
  10. 事件处理,使用inline方式的驼峰式写法,例如onClick、onChange
  11. HTML转义 --> dangerouslySetInnerHTML={{__html: '<div>hhh</div>'}}
  12. 利用es6 中 ... 展开运算符。例如
    const helloProps = {
        value: 'hello',
        show: true,
    }
 <HelloWorld ...helloProps />
  1. 如果属性值是true 这里直接写属性名。例如
    <Button disabled={true} /> 
    可以写成
    <Button disabled /> 
  1. false, null, undefined, true 是有效的子内容,但是不会被渲染出来。以下表达式渲染结果是一样的:
    <div />
    <div></div>
    <div>{false}</div>
    <div>{null}</div>
    <div>{undefined}</div>
    <div>{true}</div>

15 ...

...好吧我可能暂时想到这么多了。

【21】refs 是什么?(refs功能,如何实现?)

react 提供了一种特殊属性, 允许您直接访问DOM元素或组件实例。
ref 可以返回一个字符串(string) 或者 一个回调函数(cb),这个回调函数会在组件实例化或者销毁之后立即执行。

字符串refs在未来的版本中可能被移除,所以推荐回调的方式来代替。

    class TextInput extends Component {
        componentDidMount() {
            this.textInput.focus()
        }
        
        render() {
            return (
                <input ref={(input) => this.textInpuf = input} />
            )
        }
    }

官方推荐几种很好的方式使用refs:

  • 管理焦点,文本选择或者媒体播放
  • 触发重要的动画
  • 整合第三方DOM库

当然能不用refs就尽量不要用refs,不要太过度依赖refs来解决问题。

【22】什么是受控组件和什么是非受控组件

在react表单组件可被分为两类:受控组件 和 非受控组件。

  • 受控组件
    我们简单的理解,设置了 value 的 <input>(表单标签) 是一个受控组件。
    当我们设置了value为"hi"(某个值)时,且在页面上渲染出改值时,我们在渲染出来的元素里输入任何值都不起作用。因为react 已经把value赋值为"hi"。 要想改变value值,还必须配合这onChange 和 setState 来实现。

当然你也可以看看官网文档来如何定义受控组件的。

在 HTML 中,表单元素如 <input>,<textarea> 和 <select> 表单元素通常保持自己的状态,并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新。

我们可以通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。

话不多说,来呀,上代码:

    class App extends Component {
        constructor(props) {
            super(props)
            this.state = { value: 'hi' }
        }
        
        onInputChange = (e) => {
            this.setState({ value: e.target.value })
        }
        
        render() {
            const { value } = this.state
            return (
                <input value={value} onChange={this.onInputChange} />
            )
        }
    }

React官方推荐使用受控表单组件。总结一下上面受控组件代码更新是state的流程:

  1. 初始化state设置表单的默认值,例如 this.state = { value: 'hi' }
  2. 每当表单值发生变化时,调用onChange事件
  3. 通过对象e拿到改变的状态,例如e.target.value
  4. 通过setState更新应用value 并 触发视图重新渲染,最终完成表单组件的更新
    // 第四步 setState 我们还可以对表单值进行直接修改或者验证
    // 受控组件支持即时字段验证,允许您有条件地禁用/启用按钮,强制输入格式
    onInputChange = (e) => {
        this.setState({ value: e.target.value.substring(0, 140).toUpperCase() })
    }

特别特的注意!!!! 如果 value 为 undefined,则变成了非受控组件。

  • 非受控组件
    理解了受控组件,那你一定知道非受控组件就是没有value(单选/复选按钮为 checked)属性的表单组件。可以通过设置 defalutValue / defalutChecked 来设置组件初始值。

多啰嗦一句,defalutValue / defalutChecked,仅仅会被渲染一次,在后续渲染并不起作用。

因为不受state / props控制,我们需要为其添加 ref 来访问渲染后的DOM元素,才能最终拿到改变后的value/checked。还记得refs那句话怎么说来着:“能不用refs就尽量不要用refs”。so react官方还是比较推荐使用受控组件。

......
.....
....
...
..
.
看累了,我们留一点儿明天再来嘛。。。
.
..
...
....
.....
......

关于setState

setState 对于每一个使用过react的盆友来说应该并不陌生。与之还能立刻联想出来几个词 “更改state” “异步” “重新渲染”...

来一个道题练练手?虽然平时不会写这么*的代码,但谁知道面试会不会出现呢? 欢迎写下你的答案!

    ...
    this.state = { count : 0 }
    ...
    componentDidMount() {
        this.setState({ count: this.state.count + 1 }, () => {
            console.log(`apple...${this.state.count}`)
        })
        
        console.log(`orange...${this.state.count}`)
        
        setTimeout(() => {
            console.log(`lemen...${this.state.count}`)

            this.setState({ count: this.state.count + 1 }, () => {
                console.log(`banana...${this.state.count}`)
            })
            
            setTimeout(() => {
                console.log(`grape...${this.state.count}`)
            }, 0)
            
            this.setState({ count: this.state.count + 1 }, () => {
                console.log(`strawberry...${this.state.count}`)
            })
            
            console.log(`pear...${this.state.count}`)
        }, 0)
    }
【23】官方是这么定义setState

setState() 排队更改组件的 state ,并通过更新 state 来告诉 React ,该组件及其子组件需要重新渲染。这是用于 响应事件处理程序服务器响应 更新用户界面的主要方法。

我记得我刚学习react的时候,文档上还没有明确说调用setState是异步的,只是说了“不保证是同步的”。但最近去看了官方文档,文档说调用setState是异步的了。

【24】调用setState()实际上发生了什么?

简单的说,就是 更改state、更新UI
复杂的说,就是 怎么合并新的state,怎么根据新state来更新UI

【25】setState()第二个参数是什么?它有什么作用?

setState的第二个参数是一个可选的回调函数。这个回调函数将在 setState 完成后执行,并且重新渲染组件。在这个回调函数中你可以拿到刚更新的state的值。但是这样的逻辑 官方推荐 使用 componentDidUpdate。

【26】如何在 setState 后直接获取修改后的值
  1. setState 第二个参数,回调函数中获取
  2. 使用setTimeout
    setTimeout(() => {
        this.setState({ value: 'hhh' })
        
        console.log(this.state.value) // hhh
    }, 0)
    // 看到这里最开始的那道练手题,是不是已经可以迎刃而解了。哈哈哈哈哈
【27】setState 第一个参数有两种传递方式 1.一个对象 2. 一个函数 这两种写法有什么区别呢?

举个例子

    ...
    this.state = { text : '这是一个栗子' }
    ...

    // 使用传递对象的写法
    handleClick = () => {
        this.setState({ text: this.state.text + '111' })
        this.setState({ text: this.state.text + '222' })
    }
    
    // 使用传递函数的写法
    handleClick = () => {
        this.setState((prevState) => {
            return { text: prevState.text + '111' }
        })
        this.setState((prevState) => {
            return { text: prevState.text + '222' }
        })
    }

    render() {
        return <div onClick={this.handleClick}>{this.state.text}</div>
    }

两种传递方式,得到的结果是不一样的。

  • 传递对象 => this.state.text => '这是一个栗子222'
  • 传递函数 => this.state.text => '这是一个栗子111222'

setState为了提升性能,在批量执行 state 改变在做统一的DOM渲染。而在这个批量执行的过程中,如果你多次传递的是一堆对象,它就会做一些对象合并或者组合的操作,例如Object.assign({}, { a: '111' }, { a: '222' })。如果key值一样的话,后面的值会覆盖掉前面的值。
但多次传递函数方式,每次 React 从 setState 执行函数,并通过传递已更新的状态来更新你的状态。这使得功能 setState 可以基于先前状态设置状态。

使用setState要注意!!!

  1. setState可能会引发不必要的渲染 (shouldComponentUpdate/PureComponent)
  2. setState无法完全掌控应用中所有组件的状态(Redux/Mbox)
【28】 什么是高阶组件,它是如何使用?

高阶组件它是一个函数。高阶组件它是一个函数。高阶组件它是一个函数。并不是一个组件。通俗的讲就是它接收一个React组件作为输入,输出一个新的增强版的React组件。

举一个可能不太恰当的例子,大家可能都玩王者农药,打蓝爸爸或者红爸爸就是对英雄自身的一个增强版。吃了蓝爸爸并不会影响你吃红爸爸,也不会影响你买了什么装备等等。

好了,那么我们定义一个最最最简单的高阶组件

    const MyContainer = (WrappedComponent) => {
        return class NewComponent extend Component {
            render() {
                return <WrappedComponent />
            }
        }
    }

将你的组件类作为参数传入高阶组件这个函数即可

    class Welcome extends Component {
        ...
    }
    
    export default MyContainer(Welcome)

或者使用ES7的装饰器

    @MyContainer
    class Welcome extends Component {
        ...
    }
    export default Welcome

关于装饰器在create-react-app中的配置:

  1. npm run eject
  2. npm install --save-dev plugin-transform-decorators-legacy
  3. 在package.json中找到"babel"项,添加 "plugins": ["transform-decorators-legacy"]

在代码优化(抽离公共逻辑)或者组件解耦的时候我们可以考虑一下使用高阶组件,这样有助于提高我们代码的灵活性,逻辑的复用性。

【29】什么是PureComponent? 介绍一下PureComponent和shouldComponentUpdate有什么区别?

PureComponent 和 Component是相同,只要把继承类从 Component 换成 PureComponent 即可。PureComponent改变了shouldComponentUpdate,它会自动检查组件是否重新渲染。也就是说,只有当PureComponent检查到props或者state变化时,才会调用render函数,因此不用写额外的检查。还可以减少 Virtual DOM 的生成和比对过程,达到提升性能的目的。

注意:PureComponent 的 shouldComponentUpdate 只是进行了浅比较(state,props对象结构简单,可以理解为对象只有一层),对于复杂且嵌套更深层数据的比较会出现偏差。对于深比较,你可以选择在 shouldComponentUpdate 进行深比较检查来确定组件是否渲染,但是你要知道 深比较 是非常昂贵的。 当然,你可能知道 使用 Immutable 来帮助嵌套数据的快速比较。

【30】shouldComponentUpdate 的作用以及它的重要性?

shouldComponentUpdate 允许我们手动地判断是否要进行组件更新,根据组件的应用场景设置函数的合理返回值能够帮我们避免不必要的更新。

【31】为什么我们利用循环产生的组件中要用上key这个特殊的prop?
    // list = [{ id: 0, name: 'xiaoming', age: 18 }, { id: 1, name: 'xiaohong', age: 16 }]
    render() {
        return (
            <ul>
                list.map((item, index) => {
                    return <li key={item.id}>{item.name} - {item.age}</li>
                })
            </ul>
        )
    }

如果你没添加上 key 属性的话,会报一个警告: Warning: Each child in an array or iterator should have a unique "key" prop...

keys 是 React 用于追踪哪些列表中元素被修改被添加或者被移除的辅助标识
之所以需要key,因为react 是非常高效的,它会借助元素的 key 值来判断该元素是新创建的,或者移动(交换位置)而来的,从而减少不必要的元素重渲染。更直观一点儿就是 react 很懒,能复用的元素就复用,他不想重新创建新的元素。

那么,如果上面代码 key={index} 呢?你会发现也不会有warning,但是这样做的效率是非常非常非常低的。

看看以下例子:

    // list = [a, b, c, d]
    <div>
        list.map((item, index) => <div key={index}>{item}</div>)
    </div>

渲染完成后我们abcd 分别对应的是 0123。

    a -> 0
    b -> 1
    c -> 2
    d -> 3

假设我们只是将d的位置换到了首位 list = [d, a, b, c]

    a -> 1
    b -> 2
    c -> 3
    d -> 0

变换前和变换后,你应该发现了abcd所对应的key都改变了,这样react Virtual DOM就不论有没有相同的项,更新都会重新渲染了。所以我们要保证某个元素的 key 在其同级元素中具有唯一性,这个key 的值可以直接后台数据返回的 id,因为后台的 id 都是唯一的。

记住实际开发中,就别再直接用循环计数器 index 了,那就有点儿骗自己了哈。刚用react我也老用index...

react组件间的通信

组件之间的通信也是老生常谈了。不仅在实际开发中,面试时估计也经常被提及。

组件之间的通信大概可分为这么几种:

  1. 父组件向子组件通信
  2. 子组件向父组件通信
  3. 兄弟组件之间通信
【32】父组件向子组件通信

在 react 中数据是单向传递的,父组件可以向子组件通过传 props 的方式,子组件拿到 props 之后做相应的处理,这就是父组件向子组件进行通信方式。

    class Parent extends Component {
    
        constructor(props) {
            super(props)
            this.state = { wishes: '2018新年快乐!' }
        }
        
        render() {
            return (
                <Child title={this.state.wishes} />
            )
        }
    }
    
    class Child extends Component {
        
        render() {
            return (
                <h3>{this.props.title}</h3>
            )
        }
    }
【33】子组件向父组件通信

子组件向父组件传递数据(通信) 也是要通过 props 传递一个函数,子组件调用这个函数,并将子组件需要传递的数据作为参数,传递给父组件。

    class Parent extends Component {
    
        constructor(props) {
            super(props)
            this.state = { wishes: '2018新年快乐!' }
        }
    
        onSend = (msg) => {
            this.setState({ wishes: msg })
        }
        
        render() {
            return (
                <Child onSend={this.onSend} title={this.state.wishes} />
            )
        }
    }
    
    class Child extends Component {
    
        onChildSend = () => {
            this.props.onSend('谢谢你的祝福!')
        }
        
        render() {
            return (
                <h3 onClick={this.onChildSend}>{this.props.title}</h3>
            )
        }
    }
【34】兄弟组件之间通信

两个兄弟组件之间的数据传递,我们可以通过他们的共同父组件来实现。Child1 将要传递的信息传递给 Parent 然后 Parent 再将从 Child1 拿到的信息传递给 Child2 当然,我们同样是利用 props。

我们来写一段点击 Child1,然后将 Child1 想传递给 Child2 的信息发送到 Child2 中。

    class Parent extends Component {
    
        constructor(props) {
            super(props)
            this.state = { wishes: '' }
        }
    
        onSend = (msg) => {
            this.setState({ wishes: msg })
        }
        
        render() {
            return (
              <div>
                <Child1 onSend={this.onSend} />
                <Child2 fromChild1Wishes={this.state.wishes} />
              </div>
            )
        }
    }
    
    class Child1 extends Component {
    
        onChild1Send = () => {
            this.props.onSend('嗨,老二新年快乐!')
        }
        
        render() {
            return (
                <h3 onClick={this.onChild1Send}>我是老大Child1</h3>
            )
        }
    }
    
    class Child2 extends Component {
    
        onChild1Send = () => {
            this.props.onSend('嗨,老二新年快乐!')
        }
        
        render() {
            return (
                <div>
                    <h3>我是老二Child2</h3>
                    {
                      this.props.fromChild1Wishes ?
                      <p>来自老大的祝福 - this.props.fromChild1Wishes</p>
                      : null
                    }
                </div>
            )
        }
    }
【35】组件通信小总结

以上三种方式是最常见到的。但是实际项目中往往比这种通信更复杂得多。因为复杂项目的组件嵌套往往就像一颗枝繁叶茂的树一样。
比如:

1、跨n级组件之间通信,就是 Parent 组件和它子组件的子组件通信,或者子组件的子组件的子组件通信....

2、非嵌套组件的通信,刚刚说的兄弟组件是最简单非嵌套,还有更多不是同一父组件的非兄弟组件的嵌套。说得绕一点儿,你和你爷爷的弟弟的孙子/儿子通信就是属于这种情况。

以上的解决方案肯定是有的。

  1. 你不嫌麻烦一层一层传递 props (三层以上就不推荐)
  2. 利用 react 提供的 context , 它类似一个全局大容器,我们把想传递的信息放在里面,需要的往里面取便是。
  3. 自定义事件的方式。自定义事件是典型的发布/订阅模式,通过向事件对象上添加监听器和触发事件来实现组件间通信。
  4. 状态管理工具 mobx redux 等

多唠叨一句,所有通信方式肯定都可以用在任何项目下。但,就像女朋友一样,最适合的才是最好的

【36】ajax 应该在哪个生命周期调用呢?why

既然有人问了这个问题,看来这个问题还有有很多讨论的空间。

对于 ajax 应该是在哪个生命周期调用呢? 备受争议应该就是在 componentDidmount 和 componentWillmount 这两个生命周期之间了。网路上也众说纷纭。看过官网文档的小伙伴们应该也是知道 官网说的是 应该在 componentDidmount 。 然鹅。官网并没有告诉我们 why ?

不少开发过 react 项目的同学应该也分别尝试过在 componentDidmount 和 componentWillmount 都做过 ajax 的请求,好像没啥问题吧?好像都可以成功吧? 但是到底哪一个更合适呢?

咱们先来看点儿代码热热场子......

代码一:

    componentWillMount() {
        console.log(1)
        this.setState({ isLoading: true })
    }
    
    render() {
        console.log(2)
        return <div>test</div>
    }

代码二:

    componentWillMount() {
        console.log(1)
        setTimeout(() => {
          this.setState({ isLoading: true })
        }, 0)
    }
    
    render() {
        console.log(2)
        return <div>test</div>
    }

代码三:

    componentDidMount() {
        console.log(1)
        this.setState({ isLoading: true })
    }
    
    render() {
        console.log(2)
        return <div>test</div>
    }

代码四:

    componentDidMount() {
        console.log(1)
        setTimeout(() => {
          this.setState({ isLoading: true })
        }, 0)
    }
    
    render() {
        console.log(2)
        return <div>test</div>
    }

现在你可以告诉我代码1, 2, 3, 4分别输出的是什么?
代码一: 1, 2
代码二: 1, 2, 2
代码三: 2, 1, 2
代码四: 2, 1, 2

很多盆友都知道 this.setState 在 componentWillMount 中并不会触发 re-render。 但是如果在 setState 在一个异步方法下结果可能就不一样了。 你知道的,我们实际上获取数据都是异步的,所以并不会阻碍组件渲染。而且我们往往都会在 ajax 请求成功后再 setState 来更新状态。此时的 setState 会放入队列中,等待组件挂载完成后,再更新组件。例如 将 setState 放入 setTimeout 或者 请求成功后的 fetch 或者 axios 中都是这种情况。

所以代码二实际上就模拟了一次 在 componentWillMount 发送 ajax 请求。它的执行效果或者说效率从上面代码看上来和代码四是一样的(在 componentDidMount 发送 ajax)。所以 componentWillMount 和 componentDidMount 请求其实都是可以的!

但是!!!为什么官网没这么说呢?文档只推荐了 componentDidMount 。

React 下一代调和算法 Fiber 会通过开始或停止渲染的方式优化应用性能,其会影响到 componentWillMount 的触发次数。对于 componentWillMount 调用次数变得不可确定。 react 可能会多次频繁调用 componentWillMount 。ajax 放入这个生命周期显然不是最好的选择。

所以呢。我还是比较推荐在 componentDidMount 中调用ajax 。

更多...

当然面试中可能还会有更深层次更开发性的问题。

  • 如果你能够改进React的一样功能,那会是哪一个功能?(react 的缺点)
  • immutable.js 原理是什么? Immutable 详解及 React 中实践
  • react 性能优化有哪些?
  • react diff算法
  • react 虚拟dom原理
  • react 是什么
  • react和vue的区别
  • ...

对于react技术栈 react-router、redux 当然也有很多。

  • redux react-redux 分别负责哪些功能
  • provider connect的用法
  • store数据流向
  • redux的三个原则
  • ...

Reference

https://reactjs.org/
https://github.com/chemdemo/chemdemo.github.io/issues/14
http://www.infoq.com/cn/articles/react-jsx-and-component
https://segmentfault.com/a/1190000009001924
http://www.oschina.net/translate/functional-setstate-is-the-future-of-react
https://segmentfault.com/a/1190000007454080
https://www.jianshu.com/p/fb915d9c99c4