Mobx 给React组件添加响应式的4种方式

5,123 阅读3分钟

使用 Mobx 将一个应用变成响应式只需要简单的三个步骤即可,在第二个步骤:创建视图以响应状态的变化中,使用了 @observer 装饰器将React组件转换成响应式组件。除了@observer,还有其它三种方式可以给React组件添加响应式。

添加响应式的四种方式:

  • @observer
  • observer HOC
  • Observer component
  • userObserver hook

@observer 装饰器

observer 函数/装饰器可以用来将 React 组件转换成响应式组件。 它用 mobx.autorun 包装了组件的 render 函数以确保任何组件在渲染中使用的数据变化时都可以强制刷新组件。 observer 是由单独的 mobx-react 包提供的。

import { observer } from "mobx-react";

var timerData = observable({
    secondsPassed: 0
});

setInterval(() => {
    timerData.secondsPassed++;
}, 1000);

@observer class Timer extends React.Component {
    render() {
        return (<span>Seconds passed: { this.props.timerData.secondsPassed } </span> )
    }
};

ReactDOM.render(<Timer timerData={timerData} />, document.body);

observer HOC

observer 将组件转换成响应式组件,在 mobx-react 和 mobx-react-lite 中都有 observer,但后者的 observer 不支持将类组件转换成响应式组件。

observer<P>(baseComponent: React.FC<P>, options?: IObserverOptions): React.FC<P>

interface IObserverOptions {
    // Pass true to use React.forwardRef over the inner component. It's false by the default.
    forwardRef?: boolean
}

mobx-react 中的 observer 支持将函数组件和类组件转换成响应式组件,如果组件是函数式组件,observer 中可以使用 options 参数,类组件则没有该参数。

将函数组件转换成响应式组件

import { observer, useLocalStore } from 'mobx-react' // 6.x or mobx-react-lite@1.4.0

export const Counter = observer<Props>(props => {
  const store = useLocalStore(() => ({
    count: props.initialCount,
    inc() {
      store.count += 1
    },
  }))

  return (
    <div>
      <span>{store.count}</span>
      <button onClick={store.inc}>Increment</button>
    </div>
  )
})

observer把组件转换成响应式组件,它可以自动追踪哪个可观察量被使用了以及当值改变的时候会自动重新渲染这个组件。

将类组件转换成响应式组件

import { observer } from "mobx-react"

const TodoView = observer(
    class TodoView extends React.Component<IProps, IState> {
            store = useLocalStore(() => ({
          count: props.initialCount,
          inc() {
            store.count += 1
          },
        }))
        render() {
            return  <div>
              <span>{this.store.count}</span>
              <button onClick={this.store.inc}>Increment</button>
            </div>
        }
    }
)

observer 内部使用了React.memo,所以使用的时候我们可以不用自己再封装memo,但值得注意的是,observer内部封装的memo只是默认的浅比较,因为mobx-react认为,observed组件很少需要基于复杂的props进行更新渲染。

Observer component

<Observer>{ renderFn }</Observer>

Observer component 接收一个无参数的函数作为children,并且该函数必须返回React元素。

import { Observer, useLocalStore } from 'mobx-react';

export function ObservePerson() {
  
  const person = useLocalStore(() => ({ name: 'John' }));
  
  return (
    <div>
      {person.name} <i>I will never change my name</i>
      <div>
        <Observer>{() => <div>{person.name}</div>}</Observer>
        <button onClick={() => (person.name = 'Mike')}>
          I want to be Mike
        </button>
      </div>
    </div>
  )
}

值得注意的是,Observer component 只能使renderFn返回的组件是响应式的,如果renderFn返回的是嵌套组件,那么这个里面的组件必须得靠自己变成响应式。

看下面的示例:

import { Observer } from 'mobx-react' // 6.x or mobx-react-lite@1.4.0

function ObservePerson() {
  return (
    <Observer>
      {() => (
        <GetStore>{store => <div className="self">{store.wontSeeChangesToThis}</div>}</GetStore>
      )}
    </Observer>
  )
}

在上面的示例中, 的 renderFn 返回的是一个嵌套组件,className 为 self 的 div 就不是响应式的。如果要变成响应式,需要使用 Observer 再包装一次:

import { Observer } from 'mobx-react' // 6.x or mobx-react-lite@1.4.0

function ObservePerson() {
  return (
    <GetStore>
      {store => (
        <Observer>{() => <div>{store.changesAreSeenAgain}</div>}</Observer>
      )}
    </GetStore>
  )
}

我们还可以使用 render props 的方式代替 children 来将组件变成响应式,看下面的例子:

import { Observer, useLocalStore } from 'mobx-react';

export function ObservePerson() {
  
  const person = useLocalStore(() => ({ name: 'John' }));
  
  return (
    <div>
      {person.name} <i>I will never change my name</i>
      <div>
    
        <Observer render={() => <div>{person.name}</div>} />
    
        <button onClick={() => (person.name = 'Mike')}>
          I want to be Mike
        </button>
      </div>
    </div>
  )
}

在上面的例子中,将需要转换成响应式的组件放在了 Observer 组件的 render props 中。

useObserver hook

useObserver<T>(fn: () => T, baseComponentName = "observed", options?: IUseObserverOptions): T

interface IUseObserverOptions {
    // optional custom hook that should make a component re-render (or not) upon changes
    useForceUpdate: () => () => void
}

这个自定义的Hook 方法也可以使组件变成响应式:

import { useObserver, useLocalStore } from 'mobx-react' // 6.x or mobx-react-lite@1.4.0

function Person() {
  const person = useLocalStore(() => ({ name: 'John' }))
  return useObserver(() => (
    <div>
      {person.name}
      <button onClick={() => (person.name = 'Mike')}>No! I am Mike</button>
    </div>
  ))
}

该 Hook 方法的一个优点就是任何的hook改变 observable,组件都不会重复渲染。但也有一个缺点,就算你是在组件的某一部分使用,但是却能引起组件的全部更新。所以要慎用该hook方法。

这个自定义hook 方法存在于 mobx-react-lite 库中,或者是 mobx-react@6 中。需要注意的是,该hook方法在 mobx-react-lite 3.x 版本中已经被弃用。

参考文档:

mobx-react.js.org/observe-how

github.com/mobxjs/mobx…

github.com/mobxjs/mobx…