【问题标题】:using mobx with react functional components and without decorators使用带有反应功能组件且没有装饰器的 mobx
【发布时间】:2018-07-11 00:24:18
【问题描述】:

我正在尝试让 MobX 在反应中使用功能组件。我想这样做而不必使用装饰器。我已经使用 create-react-app 设置了一个应用程序,添加了 MobX 和 MobX-react 作为依赖项。 但是,我似乎无法让 observables 在功能组件中工作。

import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';

const Test = () => {
    extendObservable(this, {
        button: false
    });

    const handleB1 = () => {
        this.button = false;
    }

    const handleB2 = () => {
        this.button = true;
    }

    const getButton2 = () => {
        console.log('button2');
        return (
            <button type="button" onClick={handleB2}>Button 2</button>
        );
    };

    const getButton1 = () => {
        console.log('button1');
        return (
            <button type="button" onClick={handleB1}>Button 1</button>
        );
    };

    return (
        <div>
            {this.button ? getButton1() : getButton2()}
        </div>
    )
};

export default observer(Test);

单击按钮,我希望组件由于 observable 被更改而重新渲染,但我收到一个错误:

×
Error: [mobx] Invariant failed: Side effects like changing state are not 
allowed at this point. Are you trying to modify state from, for example, the render 
function of a React component? Tried to modify: ObservableObject@2.button

我曾尝试将可观察对象声明为功能组件的一部分,或者像这样声明:

const buttonState = () => {
    extendObservable(this, {
        button: false
    });
}

但在这两种情况下,我都无法让组件重新渲染,或者我不确定 observable 是否实际设置正确。

如果我把整个东西写成这样的一个类,它就可以完美地工作

import React from 'react';
import { extendObservable } from 'mobx';
import { observer } from 'mobx-react';

class Test extends React.Component {
    constructor(props) {
        super();
        extendObservable(this, {
            button: false
        });
    }

    handleB1 = () => {
        this.button = false;
    }

    handleB2 = () => {
        this.button = true;
    }

    getButton2 = () => {
        console.log('button2');
        return (
            <button type="button" onClick={this.handleB2}>Button 2</button>
        );
    };

    getButton1 = () => {
        console.log('button1');
        return (
            <button type="button" onClick={this.handleB1}>Button 1</button>
        );
    };

    render = () => {
        return  (
        <div>
            {this.button ? this.getButton1() : this.getButton2()}
        </div>
        )
    }
};

export default observer(Test);

【问题讨论】:

  • 功能组件只是一个功能,因此extendObservable(this, ... ) 不会像类示例中那样工作。如果有状态,为什么要使用函数式组件?
  • 好吧,我想使用函数式组件,因为我发现语法更易于阅读。最初我是为一个简单的删除按钮编写的,单击后会展开以显示两个按钮,一个用于确认,一个用于删除。我认为在像这样的简单组件中包含一个状态是有道理的,但现在你已经为我解释清楚了,现在它实际上是有道理的。
  • 虽然,再次查看文档后,这部分似乎是我想做的:mobx.js.org/best/…
  • 如果你有一个 Timer 对象,那会很好用,但如果它是一个 React TimerView,恐怕它不会按预期工作。

标签: reactjs mobx mobx-react


【解决方案1】:

在 React 中,函数式组件不是持久的。它们从上到下运行,返回一些 JSX,冲洗并重复。

let i = 0;

const FunctionalComp = (props) => {
  const foo = props.foo.toUpperCase();
  return <span>Rendered {i++} times. {foo}</span>;
}

所有这个功能组件都会同步创建值foo,然后返回范围。当该组件的父组件重新渲染时,该组件将执行完全相同的操作,但可能具有新值。

它永远不能做任何其他事情,这就是它强大的原因。这就是为什么我们可以称它为 functional 组件:因为它只依赖于提供给它的值,因为它不会导致改变应用程序其余部分方向的副作用,并且因为给定相同的参数,这个函数将在永恒的剩余时间内产生相同的结果。

可预测 = 强大。

现在,类组件保持持久状态。它构造、初始化其状态、方法和属性,然后渲染并返回 JSX。类(对象)仍然存在于内存中,所以它上面的所有值和方法也都存在。

类组件的方法不是那么可预测的。

class Foo {
  name = 'tommy';

  getUpperName() {
    return this.name.toUpperCase();
  }

  setName(name) {
    this.name = name;
  }
}

Foo.getUpperName 每次与相同的参数一起使用时都不会产生相同的结果(提示:它不接受任何参数并取决于它周围的上下文来确定其结果),所以其他部分应用程序可能会更改 Foo.name 并从本质上控制 Foo.getUpperName 的结果,这可能是偶然的。

类可以更新自己的状态,导致自己和所有子组件重新计算它们的 JSX 返回。

在一个普通的箭头函数中,在它返回之后,只存在它产生的返回值和函数语句(声明)。两者之间没有任何关系。

综上所述,功能组件没有绑定this。 (这在函数式编程中是一个禁忌。)它永远不会有状态。

所以你不能在函数组件中使用this 做任何事情,它不能保存可观察的值,因为每次它重新渲染它都会重新实例化每个值。

在上面的示例中,即使 this 确实引用了 Test,也会发生以下情况:

  1. 测试将创建可观察值button 作为false
  2. 测试将呈现按钮,然后您将单击该按钮。
  3. 测试将创建可观察值buttonfalse
  4. 测试将呈现按钮,然后您将单击该按钮。
  5. 测试将创建可观察值button 作为false
  6. 测试将呈现按钮,然后您将单击该按钮。

以此类推。

在 MobX 中,您的 observables 需要存在于持久数据结构上,并被传递到返回 UI 标记的渲染函数中。

const store = observable({
  name: 'tommy'
});

const changeName = () => store.name = store.name.split('').reverse().join('');

const Foo = observer((props) => {
  return (
    <button onClick={changeName}>{store.name}'s button</button>
  )
});

这不是一个很好的模式,因为FoochangeName 都不是纯粹的,至少这段代码可以工作。

你需要这样做:

const store = () => {
  const self = {};

  self.actions = {
    setName: action((name) =>  self.name = name);
  }

  return extendObservable(self, { name: 'tommy' });
}

const App = (props) => {
    return <span><Foo store={store} /></span>
}

const Foo = observer((props) => {
  return (
    <button onClick={props.store.actions.setName}>
      {store.name}'s button
    </button>
  )
})

同样,这不是一个理想的实现,但它会起作用,而且我在工作,必须回到他们付钱给我做的事情上。 ;)

【讨论】:

  • 你关于类组件的 FP 的观点是错误的,因为 useState 的行为方式与 Observable 相同。
  • 这不是一个理想的实现 - 有什么好的建议(链接,也许)?
猜你喜欢
  • 2017-03-21
  • 1970-01-01
  • 2017-01-08
  • 1970-01-01
  • 1970-01-01
  • 2014-06-17
  • 1970-01-01
  • 2016-07-17
  • 2020-12-10
相关资源
最近更新 更多