Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

在React中通过反模式获取props中函数的更新 #20

Open
fi3ework opened this issue May 27, 2018 · 3 comments
Open

在React中通过反模式获取props中函数的更新 #20

fi3ework opened this issue May 27, 2018 · 3 comments
Labels

Comments

@fi3ework
Copy link
Owner

fi3ework commented May 27, 2018

前言

在看 React 的内联函数和性能 看到了一段很有意思的代码段,乍一看挺简单的代码,但是弄懂还是认真的想了一下,在这里分享一下思考的过程。

代码

// 1. App 会传递一个 prop 给 From 表单
// 2. Form 将向下传递一个函数给 button
//    这个函数与它从 App 得到的 prop 相接近
// 3. App 会在 mounting 之后 setState,并传递
//    一个**新**的 prop 给 Form
// 4. Form 传递一个新的函数给 Button,这个函数与
//    新的 prop 相接近
// 5. Button 会忽略新的函数, 并无法
//    更新点击处理程序,从而提交陈旧的数据

class App extends React.Component {
  state = { val: "one" }

  componentDidMount() {
    this.setState({ val: "two" })
  }

  render() {
    return <Form value={this.state.val} />
  }
}

const Form = props => (
  <Button
    onClick={() => {
      submit(props.value)
    }}
  />
)

class Button extends React.Component {
  shouldComponentUpdate() {
    // 让我们假装比较了除函数以外的一切东西
    return false
  }

  handleClick = () => this.props.onClick()

  render() {
    return (
      <div>
        <button onClick={this.props.onClick}>这个的数据是旧的</button>
        <button onClick={() => this.props.onClick()}>这个工作正常</button>
        <button onClick={this.handleClick}>这个也工作正常</button>
      </div>
    )
  }
}

在线把玩地址:这是一个运行该应用程序的沙箱

上面的三个 button。

第一个会打印 "one"。

第一个会打印 "two"。

第一个会打印 "two"。

奇怪,明明长得都差不多为什么会有区别呢?

解释

第一个

首先,从上到下看,App 的 state 更新,导致 re-render。Form 是一个 stateless component,接受一个新的 prop 必然会 re-render。然后是关键的 Button,Button 将 shouldComponent 给直接 return false 了,这会导致 render 不会被再次调用,在 Button 第一次 render 后(事实上也只有一个 render,因为 shouldComponentUpdate 直接 return false 了),onClick 指向的是 prevProps 的 this.props.onClick。

在这里还需要将 JSX 还原一下方便理解,JSX 调用 React.createElemennt 生成的 VDOM 的简化版可以表达为

{
    type: button,
    onClick: this.props.onClick,
    children: "这个的数据是旧的",
    ...
}

此时,onClick 已经被赋值为了 prevProps.onClick 了,之后都不会再有任何改变。

prevProps.onClick 又是个什么样的函数呢?是这个样子的:

() => { submit(props.value) }

在第一次 render 传递给 Button 时,props.value 值为 "one",之后 Form re-render,会生成新的 onClick 函数传递给 Button,但是很遗憾,Button 内的 onClick 已经定死了,无法改变,所以总是会输出 "one"

第二次 & 第三次

第二次和第三次是一个道理,这里只说第二次。

第二次相比第一次,区别就是不是直接去执行 props.onClick,而是每次都包一个新的箭头函数,在每一次执行的时候都会去获取一个新的 this.props.onClick,这就是一切的关键,虽然 shouldComponentUpdate 为 false,但是新的 props 还是已经来了,可以通过 this.props 引用。

思考

这段代码对我们有什么启发吗?

文章中作者说可以写一个 PureComponentMinusHandlers 高阶组件,作用类似高阶组件,但是对类似 onClick 的 props 的更新函数不会触发 update(因为它们基本也不会变化),而只对数据类的 props 的变化进行 PureComponent 的 shallowCompare,这是一种 react 的优化方法。

通过之前的分析还可以玩出下面的花样:
主动拉取更新的子组件来进行性能优化:像上例中的第二种和第三种方法,将子组件的 shouldComponentUpdate 返回 false,然后在传入的 props 的 handler 外面包一层匿名函数,这样每次调用 handler 都会去访问最新的 this.props.handler 等“非计划更新的 props”(函数的 props),这些函数的 props 可以返回父组件的一些内部状态传递给子组件。如此一来,子组件就从单向状态流变成了子组件向父组件主动拉取。但这与 React 的单向数据理念相左,是属奇技淫巧。

@fi3ework fi3ework added the React label May 27, 2018
@wd2010
Copy link

wd2010 commented Aug 24, 2018

写了这么久react发现有这样的行为,但大神,我对你后面的思考方面的知识不是很理解,可以看个具体的例子吗?

@wd2010
Copy link

wd2010 commented Aug 24, 2018

@fi3ework

@fi3ework
Copy link
Owner Author

之前写的比较乱,又整理了一下哈

@fi3ework fi3ework reopened this Aug 24, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants