惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

GbyAI
GbyAI
J
Java Code Geeks
雷峰网
雷峰网
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
云风的 BLOG
云风的 BLOG
V
Visual Studio Blog
V
Vulnerabilities – Threatpost
S
Securelist
The Hacker News
The Hacker News
The Register - Security
The Register - Security
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Help Net Security
Help Net Security
G
Google Developers Blog
Hugging Face - Blog
Hugging Face - Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
M
MIT News - Artificial intelligence
AI
AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Schneier on Security
Schneier on Security
N
Netflix TechBlog - Medium
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
Hacker News - Newest:
Hacker News - Newest: "LLM"
H
Hacker News: Front Page
博客园 - 司徒正美
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
B
Blog
Microsoft Azure Blog
Microsoft Azure Blog
大猫的无限游戏
大猫的无限游戏
Security Latest
Security Latest
Engineering at Meta
Engineering at Meta
N
News and Events Feed by Topic
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
酷 壳 – CoolShell
酷 壳 – CoolShell
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
T
Threat Research - Cisco Blogs
U
Unit 42
V
V2EX
V2EX - 技术
V2EX - 技术
L
LINUX DO - 最新话题
aimingoo的专栏
aimingoo的专栏
Microsoft Security Blog
Microsoft Security Blog
Recorded Future
Recorded Future
P
Privacy & Cybersecurity Law Blog
美团技术团队
小众软件
小众软件
F
Fortinet All Blogs

CodeBlocQ

Jest - Mock Local Storage Loose assertions on arguments passed to function with Jest TypeScript Abstract Class Check if a Docker image exists locally A-Star Pathfinding React Demo My Free and Open Source Expense Tracker App is on the App Store Pass artifacts around in between stages in gitlab CI How to start a tech company as a non technical individual Setup gitment on your Hexo blog
Have Mobx and React work with TypeScript
Jonathan Klughertz · 2020-10-05 · via CodeBlocQ

The problem with Mobx and TypeScript

The official solution to support TypeScript sucks. They are basically asking you to make stores optional in your Prop types:

class MyComponent extends React.Component<{ userStore?: IUserStore; otherProp: number }, {}> {

}

then use a bang to tell the compiler your stores are present:

public render() {
const {a, b} = this.store!

}

This is truly inelegant. Below you will find a much better setup to have Mobx and React play nice with TypeScript.

TLDR;

import React from 'react';
import { inject, IWrappedComponent, observer } from 'mobx-react';
import { MyStoreClass } from './myStore';

type StoreProps = {
myStore: MyStoreClass;
};

interface Props extends StoreProps {
realProp: string;
}

@inject('myStore')
@observer
class App extends React.Component<Props> {
static defaultProps = {} as StoreProps;

render() {
const { myStore, realProp } = this.props;
[...]
}
}

export default App as typeof App & IWrappedComponent<Props>;

Working Example

Explanations and Steps to convert your code

Consider the following example, a simple counter app with a store injected through the context.


import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import counterStore from './counterStore';

import App from './App';

const rootElement = document.getElementById('root');
render(
<Provider counterStore={counterStore}>
<App title='normal prop title' />
</Provider>,
rootElement
);


import { observable } from 'mobx';

export class CounterStore {
@observable counter = 0;

increment() {
this.counter++;
}

decrement() {
this.counter--;
}
}

export default new CounterStore();


import React from 'react';
import { inject, observer } from 'mobx-react';

@inject('counterStore')
@observer
class App extends React.Component {
render() {
const { counterStore, title } = this.props;

return (
<div>
<div>{title}</div>
<button onClick={() => counterStore.increment()}>+1</button>
<span>{counterStore.counter}</span>
<button onClick={() => counterStore.decrement()}>-1</button>
</div>
);
}
}

export default App;

Converting to TypeScript

Important:

  • This will only work with Mobx 5 as v6 dropped decorators support. Discussion
  • You will also need to stay on mobx-react v6 to be compatible with mobx 5.
  • If you are starting from scratch and you can get away with only injecting your stores through hooks, you don’t need all this.

1. Filenames

Convert all the files to .ts and .tsx

2. Convert the store to TS

Pretty straightforward, add the type for counter:

[...]
@observable counter: number = 0;
[...]

3. Typed Props for the App Component

Let’s split the injected stores props and the actual props (you will see why later).

import { CounterStore } from './counterStore';

type StoreProps = {
counterStore: CounterStore;
};

interface Props extends StoreProps {
title: string;
}

@inject('counterStore')
@observer
class App extends React.Component<Props> {
[...]
};

export default App;

The warnings in our App component are now gone but the index.ts file is complaining about the missing counterStore.

4. Fixing the App Component exported type

First of all we need to specify defaultProps so that the compiler does not expect us to pass the store down

@inject('counterStore')
@observer
class App extends React.Component<Props> {
static defaultProps = {} as StoreProps;

render() {
[...]

then we need to adjust the type of the default export (our Component). This will help TypeScript recognize the fact that App has a static wrappedComponent property.

This is especially useful in unit tests as you will probably want to test App.wrappedComponent instead of App.

Here is how to export the correct type:

export default App as (typeof App & IWrappedComponent<Props>);

Your app is now ready to go !

5. Full code


import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'mobx-react';
import store from './counterStore';

import App from './App';

const rootElement = document.getElementById('root');
render(
<Provider counterStore={store}>
<App title='normal prop title' />
</Provider>,
rootElement
);


import { observable } from 'mobx';

export class CounterStore {
@observable counter: number = 0;

increment() {
this.counter++;
}

decrement() {
this.counter--;
}
}

export default new CounterStore();


import React from 'react';
import { inject, IWrappedComponent, observer } from 'mobx-react';
import { CounterStore } from './counterStore';

type StoreProps = {
counterStore: CounterStore;
};

interface Props extends StoreProps {
title: string;
}

@inject('counterStore')
@observer
class App extends React.Component<Props> {
static defaultProps = {} as StoreProps;

render() {
const { counterStore, title } = this.props;

return (
<div>
<div>{title}</div>
<button onClick={() => counterStore.increment()}>+1</button>
<span>{counterStore.counter}</span>
<button onClick={() => counterStore.decrement()}>-1</button>
</div>
);
}
}

export default App as typeof App & IWrappedComponent<Props>;