pnpm create vite
pnpm install @babel/core @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
pnpm install mobx mobx-react
vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react({
babel: {
plugins: [
["@babel/plugin-proposal-decorators", { legacy: true }],
["@babel/plugin-proposal-class-properties", { loose: true }],
],
},
})]
})
jsconfig.json
{
"compilerOptions": {
"experimentalDecorators": true
}
}
src\main.tsx
import {observable} from 'mobx';
console.log(observable);
makeObservable
为每个属性指定一个注解。 最重要的注解如下:observable
定义一个存储 state 的可追踪字段。action
将一个方法标记为可以修改 state 的 action。computed
标记一个可以由 state 派生出新的值并且缓存其输出的 getter。observable(source, overrides?, options?)
source
对象将会被克隆并且所有的成员都将会成为可观察的observable
返回的对象将会使用 Proxy 包装,这意味着之后被添加到这个对象中的属性也将被侦测并使其转化为可观察对象import {observable,reaction} from 'mobx';
let obj = {name:'1'}
let proxyObj = observable(obj);
console.log(proxyObj);
reactions
的目的是对自动发生的副作用进行建模。 它们的意义在于为你的可观察状态创建消费者,以及每当关联的值发生变化时,自动运行副作用autorun(effect: (reaction) => void)
observable
或者 computed
注解的Autorun
通过在响应式上下文运行 effect
来工作。在给定的函数执行期间,MobX 会持续跟踪被 effect 直接或间接读取过的所有可观察对象和计算值。 一旦函数执行完毕,MobX 将收集并订阅所有被读取过的可观察对象,并等待其中任意一个再次发生改变。 一旦有改变发生,autorun 将会再次触发,重复整个过程import {observable,autorun} from 'mobx';
let obj = {name:'1'}
let proxyObj = observable(obj);
autorun(()=>{
console.log(proxyObj.name);
})
proxyObj.name = '2';
makeObservable(target, annotations?, options?)
import {observable,makeObservable,autorun} from 'mobx';
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
})
this.value = value
}
}
const doubler = new Doubler(1);
autorun(()=>{
console.log(doubler.value);
});
doubler.value = 2;
JavaScript getters
上添加 computed
注解来创建。 使用 makeObservable
将 getter 声明为 computed
。或者如果你希望所有的 getters 被自动声明为 computed,可以使用 makeAutoObservable
+import {observable,makeObservable,autorun,computed} from 'mobx';
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
+ double: computed,
})
this.value = value
}
+ get double() {
+ return this.value * 2
+ }
}
const doubler = new Doubler(1);
autorun(()=>{
console.log(doubler.value);
+ console.log(doubler.double);
});
doubler.value = 2;
+import {observable,makeObservable,autorun,computed,action} from 'mobx';
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
+ increment: action
})
this.value = value
}
get double() {
return this.value * 2
}
+ increment() {
+ this.value++
+ this.value++
+ }
}
const doubler = new Doubler(1);
autorun(()=>{
console.log(doubler.value);
console.log(doubler.double);
});
+doubler.increment();
+import {observable,makeObservable,autorun,computed,flow,action} from 'mobx';
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
increment: action,
+ fetch: flow
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
+ *fetch() {
+ const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
+ this.value = response;
+ }
}
const doubler = new Doubler(1);
autorun(()=>{
console.log(doubler.value);
console.log(doubler.double);
});
doubler.increment();
+doubler.fetch();
autoBind
选项import {observable,makeObservable,autorun,computed,flow,action} from 'mobx';
class Doubler {
value
constructor(value) {
makeObservable(this, {
value: observable,
double: computed,
+ increment: action.bound,
+ fetch: flow.bound
})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
*fetch() {
const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
this.value = response;
}
}
const doubler = new Doubler(1);
autorun(()=>{
console.log(doubler.value);
console.log(doubler.double);
});
+const increment = doubler.increment;
+increment();
+const fetch = doubler.fetch;
+fetch();
makeAutoObservable(target, overrides?, options?)
+import {observable,makeObservable,autorun,computed,flow,action,makeAutoObservable} from 'mobx';
class Doubler {
+ PI=3.14
value
constructor(value) {
+ makeAutoObservable(this,{PI:false},{autoBind:true})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
*fetch() {
const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
this.value = response;
}
}
const doubler = new Doubler(1);
+autorun(()=>{
+ console.log(doubler.PI);
+});
autorun(()=>{
console.log(doubler.value);
console.log(doubler.double);
});
const increment = doubler.increment;
increment();
const fetch = doubler.fetch;
fetch();
+doubler.PI=3.15;
reaction(() => value, (value, previousValue, reaction) => { sideEffect }, options?).
+import {makeAutoObservable,reaction} from 'mobx';
class Doubler {
PI=3.14
value
constructor(value) {
makeAutoObservable(this,{PI:false},{autoBind:true})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
*fetch() {
const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
this.value = response;
}
}
const doubler = new Doubler(1);
+reaction(
+ () => doubler.value,
+ value => {
+ console.log('value',value);
+ }
+)
+doubler.value=2;
when(predicate: () => boolean, effect?: () => void, options?)
+import {makeAutoObservable,reaction,when} from 'mobx';
class Doubler {
PI=3.14
value
constructor(value) {
makeAutoObservable(this,{PI:false},{autoBind:true})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
*fetch() {
const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
this.value = response;
}
}
const doubler = new Doubler(1);
+when(
+ () => doubler.value === 3,
+ () => {
+ console.log('value',doubler.value);
+ }
+)
+doubler.value++;
+doubler.value++;
+doubler.value++;
runInAction(fn)
action
。在异步进程中非常有用import {makeAutoObservable,reaction,when,autorun,runInAction} from 'mobx';
class Doubler {
PI=3.14
value
constructor(value) {
makeAutoObservable(this,{PI:false},{autoBind:true})
this.value = value
}
get double() {
return this.value * 2
}
increment() {
this.value++
this.value++
}
*fetch() {
const response = yield new Promise((resolve)=>setTimeout(()=>resolve(5),1000))
this.value = response;
}
}
const doubler = new Doubler(1);
+autorun(()=>console.log(doubler.value));
+runInAction(()=>{
+ doubler.value++;
+ doubler.value++;
+ doubler.value++;
+});
src\main.jsx
import { createRoot } from "react-dom/client";
import Counter from "./Counter";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<Counter/>);
observer<P>(baseComponent: FunctionComponent<P>): FunctionComponent<P>
React.memo
自动应用于提供给观察者的功能组件this.props
和this.state
变得可观察,因此组件将对渲染使用的属性和状态的所有更改作出反应src\Counter.jsx
import {makeAutoObservable} from 'mobx';
import {observer} from 'mobx-react';
class Store {
number=1
constructor(){
makeAutoObservable(this,{},{autoBind:true});
}
add(){
this.number++;
}
}
let store=new Store();
export default observer(function () {
return (
<div>
<p>{store.number}</p>
<button onClick={store.add}>+</button>
</div>
)
});
src\Counter.jsx
import React from 'react';
import {makeAutoObservable} from 'mobx';
import {observer} from 'mobx-react';
class Store {
number=1
constructor(){
makeAutoObservable(this,{},{autoBind:true});
}
add(){
this.number++;
}
}
let store=new Store();
+@observer
+export default class Counter extends React.Component{
render(){
return (
<div>
<p>{store.number}</p>
<button onClick={store.add}>+</button>
</div>
)
}
+}
<Observer>{renderFn}</Observer>
Observer
是一个React组件,它将观察者应用于组件中的匿名区域。它将单个无参数函数作为子函数,该函数应只返回一个React组件。将跟踪函数中的渲染,并在需要时自动重新渲染src\Counter.jsx
import React from 'react';
import {makeAutoObservable} from 'mobx';
+import {observer,Observer} from 'mobx-react';
class Store {
number=1
constructor(){
makeAutoObservable(this,{},{autoBind:true});
}
add(){
this.number++;
}
}
let store=new Store();
export default function () {
return (
+ <Observer>
{
()=>(
<>
<p>{store.number}</p>
<button onClick={store.add}>+</button>
</>
)
}
+ </Observer>
)
}
src\Counter.jsx
import React from 'react';
import {makeAutoObservable} from 'mobx';
import {observer,Observer,useObserver} from 'mobx-react';
class Store {
number=1
constructor(){
makeAutoObservable(this,{},{autoBind:true});
}
add(){
this.number++;
}
}
let store=new Store();
export default function () {
+ return useObserver(()=>(
<>
<p>{store.number}</p>
<button onClick={store.add}>+</button>
</>
+ ));
}
useLocalObservable<T>(initializer: () => T, annotations?: AnnotationsMap<T>): T
src\Counter.jsx
import React from 'react';
import {makeAutoObservable} from 'mobx';
import {observer,Observer,useObserver,useLocalObservable} from 'mobx-react';
export default function () {
+ const store = useLocalObservable(()=>({
+ number:1,
+ add(){
+ this.number++;
+ }
+ }));
return useObserver(()=>(
<>
<p>{store.number}</p>
<button onClick={store.add}>+</button>
</>
));
}
import { createRoot } from "react-dom/client";
import App from "./App";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(<App/>);
src\App.jsx
import React from "react";
import store from "./store";
import StoreContext from './context';
import Todos from './Todos';
import User from './User';
const App = () => {
return (
<StoreContext.Provider value={store}>
<User />
<hr />
<Todos/>
</StoreContext.Provider>
);
};
export default App;
src\context.jsx
import React from "react";
const StoreContext = React.createContext();
export default StoreContext;
src\User.jsx
import { useContext,useRef } from 'react';
import StoreContext from './context';
import { observer } from "mobx-react";
const User = observer(function () {
const { userStore } = useContext(StoreContext);
const ref = useRef(null);
return (
<div>
{userStore.isLogin?(
<>
{userStore.username}<button onClick={() => userStore.logout()}>退出</button>
</>
):(
<>
<input ref={ref} type="text" />
<button onClick={() => userStore.login(ref.current.value)}>登录</button>
</>
)}
</div>
);
});
export default User;
src\Todos.jsx
import { useContext ,useRef} from 'react';
import StoreContext from './context';
import { observer } from "mobx-react";
import {TodoStore} from './store';
const Todo = observer(function ({todo}) {
return (
<li>
<input
type="checkbox"
checked={todo.completed}
onChange={() => todo.toggle()}
/>
{todo.text}
</li>
);
});
const TodoList = observer(function () {
const { todoStore } = useContext(StoreContext);
return (
<div>
<ul>
{todoStore.list.map((todo, index) => (
<Todo todo={todo} key={index} />
))}
</ul>
</div>
);
});
const TodoLeft = observer(function () {
const { todoStore } = useContext(StoreContext);
return <>未完成: {todoStore.unCompletedCount}</>;
});
const AddTodo = observer(function AddTodo() {
const { todoStore } = useContext(StoreContext);
const ref = useRef(null);
return (
<>
<input ref={ref} type="text" />
<button
onClick={() => {
const item = new TodoStore(ref.current.value);
todoStore.add(item);
ref.current.value = "";
}}
>新增</button>
</>
);
});
export default observer(function () {
return (
<>
<AddTodo />
<TodoList />
<TodoLeft />
</>
);
});
src\store\user\index.jsx
import { makeAutoObservable } from "mobx";
class UserStore {
username='';
constructor() {
makeAutoObservable(this, {}, { autoBind: true });
}
get isLogin() {
return this.username.length > 0;
}
login(username) {
this.username = username;
}
logout() {
this.username = "";
}
}
const userStore = new UserStore();
export default userStore;
src\store\todos\index.jsx
import { makeAutoObservable } from "mobx";
class TodoStore {
list = [];
get unCompletedCount() {
return this.list.filter((todo) => !todo.completed).length;
}
constructor() {
makeAutoObservable(this,{},{autoBind: true});
}
add(todo) {
this.list.push(todo);
}
}
const todoStore = new TodoStore();
export default todoStore;
src\store\todos\todo.jsx
import { makeAutoObservable } from "mobx";
export class TodoStore {
text = "";
completed = false;
constructor(text) {
makeAutoObservable(
this,{},{autoBind: true}
);
this.text = text;
}
toggle() {
this.completed = !this.completed;
}
}
src\store\index.jsx
import todoStore from "./todos";
import userStore from "./user";
const store = { todoStore, userStore };
export { TodoStore } from "./todos/todo";
export default store;