JNUfeatherwit / blog

6 stars 0 forks source link

React的一些小tips #6

Open JNUfeatherwit opened 6 years ago

JNUfeatherwit commented 6 years ago

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)有哪些优缺点

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

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

### 5.智能组件 vs 木偶组件 ? 或者 容器组件 vs 展示组件 ?
mart 组件 和 Dumb 组件对于开发过 react 项目的朋友来说应该不陌生了。

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

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

小提示2:Dumb 组件 的子组件也应该是 Dumb 组件。
### 6.React生命周期
![](https://github.com/JNUfeatherwit/blog/blob/master/images/Life.png?raw=true)
react 组件的生命周期方法都可以被分割成四个阶段:初始化、挂载阶段(Mounting)、更新阶段(Updating)、卸载阶段(Unmounting)。

接下来就让我们去看看生命周期都有哪些小知识点。
### 7.Mounting(挂载) -- 下面这些方法将会在 component 实例被创建和插入到DOM后调用。
- constructor()
- componentWillMount()
- render()
- componentDidMount()
### 8.Updating -- props 或者 state 的变化都会导致更新。下面这些方法会在 component 重新渲染时调用。
- componentWillReceiveProps()
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
### 9.Unmounting -- 该方法将会在 component 从DOM中移除时调用。
- componentWillUnmount()
## 接下来简单的介绍一下几个生命周期。
### 10. componentWillMount
componentWillMout() 是在组件挂载(mount)之前被调用. componentWillMount()是唯一一个在服务器端渲染(ssr)调用的生命周期钩子

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

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

在这个钩子里合适:

- ajax 请求
- 初始化DOM节点的操作
- 设置计时器 setTimeout 或者 setInterval (温馨提示,别忘了在 componentWillUnmount 关闭这些计时器)
- 关于 setState 在 componentDidMount 使用: 可以使用。但是经常导致性能问题。当然非要在 render 前拿到 DOM 节点的大小和位置,是可以用的。

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

在这个钩子里合适:

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

关于setState在componentWillReceiveProps使用: 可以使用。
### 13. shouldComponentUpdate(nextProps, nextState)
当改变state 或者 props 并且是在render之前会调用shouldComponentUpdate,说白了就是该钩子函数用于告诉 React 组件是否需要重新渲染。

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

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

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

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

### 14. componentWillUpdate(nextProps, nextState)
state 或者 props 更新后 re-render 之前调用。

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

### 15. componentDidUpdate(prevProps, prevState)
在组件更新之后马上调用 componentDidUpdate。

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

- 操作 DOM
- 发起网络请求
### 16. componentWillUnmount
在组件卸载(unmounted)和销毁(destroyed)前调用。

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

- 清除计时器
- 断开网络请求
- 解绑dom事件
- 等等
### 17.生命周期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循环不能陷入死循环。
### 18.什么是受控组件和什么是非受控组件
在react表单组件可被分为两类:受控组件 和 非受控组件。
 - 受控组件
我们简单的理解,设置了 value 的 <input>(表单标签) 是一个受控组件。
当我们设置了value为"hi"(某个值)时,且在页面上渲染出改值时,我们在渲染出来的元素里输入任何值都不起作用。因为react 已经把value赋值为"hi"。 要想改变value值,还必须配合这onChange 和 setState 来实现。
当然你也可以看看官网文档来如何定义受控组件的。
> 在 HTML 中,表单元素如input,textarea和select表单元素通常保持自己的状态,
并根据用户输入进行更新。而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新。
> 我们可以通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。
- 非受控组件
理解了受控组件,那你一定知道非受控组件就是没有value(单选/复选按钮为 checked)属性的表单组件。可以通过设置 defalutValue / defalutChecked 来设置组件初始值。

### 19.如何在 setState 后直接获取修改后的值
1.setState 第二个参数,回调函数中获取
2.使用setTimeout
setTimeout(() => {
    this.setState({ value: 'hhh' })

    console.log(this.state.value) // hhh
}, 0)
### 20. 什么是高阶组件,它是如何使用?
高阶组件它是一个函数。高阶组件它是一个函数。高阶组件它是一个函数。并不是一个组件。通俗的讲就是它接收一个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

在代码优化(抽离公共逻辑)或者组件解耦的时候我们可以考虑一下使用高阶组件,这样有助于提高我们代码的灵活性,逻辑的复用性。
### 21.什么是PureComponent? 介绍一下PureComponent和shouldComponentUpdate有什么区别?
PureComponent 和 Component是相同,只要把继承类从 Component 换成 PureComponent 即可。PureComponent改变了shouldComponentUpdate,它会自动检查组件是否重新渲染。也就是说,只有当PureComponent检查到props或者state变化时,才会调用render函数,因此不用写额外的检查。还可以减少 Virtual DOM 的生成和比对过程,达到提升性能的目的。

注意:PureComponent 的 shouldComponentUpdate 只是进行了浅比较(state,props对象结构简单,可以理解为对象只有一层),对于复杂且嵌套更深层数据的比较会出现偏差。对于深比较,你可以选择在 shouldComponentUpdate 进行深比较检查来确定组件是否渲染,但是你要知道 深比较 是非常昂贵的。 当然,你可能知道 使用 Immutable 来帮助嵌套数据的快速比较。
### 22.为什么我们利用循环产生的组件中要用上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]

list.map((item, index) =>
{item}
)
渲染完成后我们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 都是唯一的。
### 23.父子组件之间传递的props数据尽量简单扁平,尽量不要传递复杂的数据(如对象),避免触发不必要的渲染,但是使用mobx例外,传递的数据如果是obserable对象里的属性,要传递对象,在子组件里再进行解构