大前端 - react - mobx6更新

代码地址:

MobX6

1. MobX 概述

MobX 是一个简单的可扩展的状态管理库,无样板代码风格简约。

目前最新版本为 6,版本 4 和版本 5 已不再支持。

在 MobX 6 中不推荐使用装饰器语法,因为它不是 ES 标准,并且标准化过程要花费很长时间,但是通过配置仍然可以启用装饰器语法。

MobX 可以运行在任何支持 ES5 的环境中,包含浏览器和 Node。

MobX 通常和 React 配合使用,但是在 AngularVue 中也可以使用 MobX。

2. MobX 光速入门

2.1 下载
  • mobx:MobX 核心库
  • mobx-react-lite:仅支持函数组件
  • mobx-react:既支持函数组件也支持类组件

yarn add mobx@6.1.8 mobx-react-lite@3.2.0

2.2 核心概念
  1. observable state:被 MobX 跟踪的状态。
  2. action:允许修改状态的方法,在严格模式下只有 action 方法被允许修改状态。
  3. computed:根据应用程序状态派生的新值,计算值。
2.3 工作流程

在这里插入图片描述

2.4 入门案例
2.4.1 案例

计数器:在组件中显示数值状态,单击按钮使数值加一,单击按钮使数值重置。

// Counter Component -> Counter 组件
// observer: 监控当前组件使用到的由 MobX 跟踪的 observable state, 当状态发生变化时通知 React 更新视图
import { observer } from "mobx-react-lite"

function Counter({ counterStore }) {
  return (
    <div>
      <p className="paragraph">{counterStore.count}</p>
      <button onClick={() => counterStore.increment()} className="button">加 1</button>
      <button onClick={() => counterStore.reset()} className="button">重置</button>
    </div>
  )
}

export default observer(Counter)
// Counter Store -> 管理 Counter 组件的 Store
import { makeAutoObservable } from "mobx"

class CounterStore {
  // 数值状态
  count = 10
  constructor() {
    // 将参数对象中的属性设置为 observable state
    // 将参数对象中的方法设置为 action
    makeAutoObservable(this)
  }
	// 使数值状态加一
  increment() {
    this.count += 1
  }
	// 重置数值状态
  reset() {
    this.count = 0
  }
}

export default CounterStore
// App Component -> 根组件
// 导入 Counter 组件
import Counter from "./components/Counter/Counter"
// 导入管理 Counter 组件的 Store
import CounterStore from "./stores/Counter/CounterStore"
// 创建管理 Counter 组件的 Store 实例对象
const counterStore = new CounterStore()

function App() {
  // 调用 Counter 组件并传入管理其状态的 Store
  return <Counter counterStore={counterStore} />
}

export default App
2.4.2 makeAutoObservable
// target: 将目标对象中的属性和方法设置为 observable state 和 action
// overrides: 覆盖默认设置, 将 target 对象中的某些属性或者方法设置为普通属性
// options: 配置对象, autoBind, 使 action 方法始终拥有正确的 this 指向
makeAutoObservable(target, overrides?, options?)
makeAutoObservable(this, {reset: false}, {autoBind: true})
2.4.3 总结

状态变化更新视图的必要条件

  1. 状态需要被标记为 observable state
  2. 更改状态的方法需要被标记为 action 方法
  3. 组件视图必须通过 observer 方法包裹

可以使用 makeAutoObservable 方法将对象属性设置为 observable state,将对象方法设置为 action 方法

可以使用 observer 方法监控当前组件使用到的由 MobX 跟踪的 observable state,当状态发生变化时通知 React 更新视图

3. TodoList

3.1 创建初始 TodoListStore
  1. 创建 TodoListStore,用于管理待办事项列表状态。

    // TodoListStore.js
    class TodoListStore {
      todos = []
      constructor(todos) {
        if (todos) this.todos = todos
      }
    }
    export default TodoListStore
    
  2. 创建 TodoViewStore,用于管理待办事项状态。

    // TodoViewStore.js
    class TodoViewStore {
      id = Math.random()
      title = ""
      completed = false
      constructor(title) {
        this.title = title
      }
    }
    export default TodoViewStore
    
  3. 创建 TodoListStore 实例对象并将其传入到 TodoListView 组件

    // App.js
    import TodoListView from "./components/Todos/TodoListView"
    import TodoListStore from "./stores/Todos/TodoListStore"
    import TodoStore from "./stores/Todos/TodoStore"
    
    const counterStore = new CounterStore()
    
    const todoListStore = new TodoListStore([
      new TodoStore("Hello MobX"),
      new TodoStore("Hello React")
    ])
    
    function App() {
      return <TodoListView TodoListStore={todoListStore} />
    }
    
  4. 渲染初始待办事项列表

    // TodoListView.js
    function TodoListView({ TodoListStore }) {
      return (
        <ul className="todo-list">
          {TodoListStore.todos.map(todo => (
            <TodoView key={todo.id} todo={todo} />
          ))}
        </ul>
      )
    }
    
    // TodoView.js
    function TodoView({ todo }) {
      return <label>{todo.title}</label>
    }
    
3.2 创建待办事项
  1. 在 TodoListStore 类中创建 createTodo 方法,用于向 todos 数组中添加待办事项

    // TodoListStore.js
    import TodoStore from "./TodoStore"
    
    class TodoListStore {
      createTodo(title) {
        this.todos.push(new TodoStore(title))
      }
    }
    
  2. 在 TodoHeader 组件中调用 createTodo 方法创建待办事项

    // TodoListView.js
    function TodoListView({ TodoListStore }) {
      return <TodoHeader createTodo={title => TodoListStore.createTodo(title)} />
    }
    
    // TodoHeader.js
    import { useState } from "react"
    
    function TodoHeader({ createTodo }) {
      const [title, setTitle] = useState("")
      return (
        <header className="header">
          <input
            value={title}
            onChange={event => setTitle(event.target.value)}
            onKeyUp={event => {
              if (event.key === "Enter") {
                createTodo(title)
                setTitle("")
              }
            }}
          />
        </header>
      )
    }
    
  3. 设置状态变化更新视图的必要条件

    // TodoListStore.js
    import { action, makeObservable, observable } from "mobx"
    
    class TodoListStore {
      constructor() {
        makeObservable(this, {
          todos: observable,
          createTodo: action
        })
      }
    }
    
    // TodoListView.js
    import { observer } from "mobx-react-lite"
    
    function TodoListView (){}
    
    export default observer(TodoListView)
    
3.3 创建 Store 上下文

我们希望在每个组件中都能够直接获取到 TodoListStore,而不是通过 props 属性传递的方式。

// TodoListStore.js
import { createContext, useContext } from "react"

const TodoListStoreContext = createContext()

const TodoListStoreProvider = ({ store, children }) => {
  return (
    <TodoListStoreContext.Provider value={store}>
      {children}
    </TodoListStoreContext.Provider>
  )
}

const useTodoStore = () => {
  return useContext(TodoListStoreContext)
}

export { TodoListStore, TodoListStoreProvider, useTodoStore }
// App.js
import { TodoListStore, TodoListStoreProvider } from "./stores/Todos/TodoListStore"

function App() {
  return (
    <TodoListStoreProvider store={todoListStore}>
      <TodoListView/>
      <Counter counterStore={counterStore} />
    </TodoListStoreProvider>
  )
}
// TodoListView.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"

function TodoListView() {
  const todoListStore = useTodoListStore()
}
import { useTodoListStore } from "../../stores/Todos/TodoListStore"

function TodoHeader() {
  const todoListStore = useTodoListStore()
  todoListStore.createTodo(title)
}
3.4 删除待办事项
// TodoListStore.js
class TodoListStore {
  constructor() {
    makeObservable(this, {
      removeTodo: action
    })
  }
  removeTodo(id) {
    const index = this.todos.findIndex(todo => todo.id === id)
    this.todos.splice(index, 1)
  }
}
// TodoView.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"

function TodoView({ todo }) {
  const todoListStore = useTodoListStore()
  return <button onClick={() => todoListStore.removeTodo(todo.id)} className="destroy" />
}
3.5 更改任务状态
// TodoStore.js
import { makeObservable, observable, action } from "mobx"

class TodoStore {
  completed = false
  constructor() {
    makeObservable(this, {
      completed: observable,
      toggle: action
    })
  }
  toggle() {
    this.completed = !this.completed
  }
}
// TodoView.js
import { observer } from "mobx-react-lite"

function TodoView({ todo }) {
  return (
    <li className={todo.completed ? "completed" : ""}>
      <input checked={todo.completed} onChange={() => todo.toggle()} className="toggle" type="checkbox" />
    </li>
  )
}

export default observer(TodoView)
3.6 更正 this 指向
<input onChange={() => todo.toggle()} />
<input onChange={todo.toggle}/> // this 指向会发生错误
makeObservable(this, {
  toggle: action.bound
})
3.7 计算待办事项数量

待办事项数量属于派生状态,即该状态依赖现有状态 (todos) 生成。

派生状态可以使用计算值实现,当依赖状态发生变化后,计算值自动更新。

// TodoListStore.js
import { computed } from "mobx"

class TodoListStore {
  constructor() {
    makeObservable(this, {
      unCompletedTodoCount: computed
    })
  }
  get unCompletedTodoCount() {
    return this.todos.filter(todo => !todo.completed).length
  }
}
// TodoFooter.js
import { useTodoListStore } from "../../stores/Todos/TodoListStore"
import { observer } from "mobx-react-lite"

function TodoFooter() {
  const todoListStore = useTodoListStore()
  return <strong>{todoListStore.unCompletedTodoCount}</strong> item left
}

export default observer(TodoFooter)

注意:计算值是被缓存的。

get unCompletedTodoCount() {
  console.log("unCompletedTodoCount")
  return this.todos.filter(todo => !todo.completed).length
}
{todoListStore.unCompletedTodoCount}
{todoListStore.unCompletedTodoCount}
{todoListStore.unCompletedTodoCount}
// 计算属性被调用多次, 但是方法内部的console.log 只会输出一次, 说明计算属性是被缓存的.
3.8 待办事项过滤

待办事项过滤为计算值的练习任务。

// TodoListStore.js
class TodoListStore {
  todos = []
  filter = "all"
  constructor() {
    makeObservable(this, {
      filter: observable,
      changeFilter: action,
      filterTodos: computed
    })
  }
  get filterTodos() {
    switch (this.filter) {
      case "all":
        return this.todos
      case "active":
        return this.todos.filter(todo => !todo.completed)
      case "completed":
        return this.todos.filter(todo => todo.completed)
      default:
        return this.todos
    }
  }
  changeFilter(filter) {
    this.filter = filter
  }
}
// TodoFooter.js
function TodoFooter() {
  const todoListStore = useTodoListStore()
  return (
    <footer className="footer">
      <ul className="filters">
        <li>
          <button
            onClick={() => todoListStore.changeFilter("all")}
            className={todoListStore.filter === "all" ? "selected" : ""}
          >
            All
          </button>
        </li>
        <li>
          <button
            onClick={() => todoListStore.changeFilter("active")}
            className={todoListStore.filter === "active" ? "selected" : ""}
          >
            Active
          </button>
        </li>
        <li>
          <button
            onClick={() => todoListStore.changeFilter("completed")}
            className={todoListStore.filter === "completed" ? "selected" : ""}
          >
            Completed
          </button>
        </li>
      </ul>
    </footer>
  )
}
// TodoListView.js
function TodoListView() {
  const todoListStore = useTodoListStore()
  return (
    <ul className="todo-list">
      {todoListStore.filterTodos.map(todo => <TodoView key={todo.id} todo={todo} />)}
    </ul>
  )
}
3.9 加载远端任务
  1. 下载并启动 json-server

    npm install -g json-server

    json-server ./src/todo.json --port 3005

    yarn add axios

  2. 创建 loadTodos 方法用于加载初始任务

    // TodoListStore.js
    import axios from "axios"
    import { runInAction } from "mobx"
    
    class TodoListStore {
      constructor(todos) {
        this.loadTodos()
      }
      async loadTodos() {
        let todos = await axios.get("http://localhost:3005/todos").then(response => response.data)
        runInAction(() => todos.forEach(todo => this.todos.push(todo)))
      }
    }
    
  3. 解决远端加载待办事项无法切换状态的问题

    原因是远端加载待办事项的任务对象的原型对象上没有 toggle 方法。

    import TodoStore from "./TodoStore"
    
    class TodoListStore {
      async loadTodos() {
        runInAction(() => todos.forEach(todo => this.todos.push(new TodoStore(todo.title))))
      }
    }
    
3.9 创建 RootStore

通过创建 RootStore 可以将 CounterStore 和 TodoListStore 进行合并,实现在任何组件中都可以访问任何状态,方便全局状态共享。

// RootStore.js
import CounterStore from "./Counter/CounterStore"
import TodoListStore from "./Todos/TodoListStore"
import { createContext, useContext } from "react"

class RootStore {
  constructor() {
    this.counterStore = new CounterStore()
    this.todoListStore = new TodoListStore()
  }
}

const RootStoreContext = createContext()

const RootStoreProvider = ({ store, children }) => {
  return (
    <RootStoreContext.Provider value={store}>
      {children}
    </RootStoreContext.Provider>
  )
}

const useRootStore = () => {
  return useContext(RootStoreContext)
}

export { RootStore, RootStoreProvider, useRootStore }
// App.js
import Counter from "./components/Counter/Counter"
import TodoListView from "./components/Todos/TodoListView"
import { RootStore, RootStoreProvider } from "./stores/RootStore"

const rootStore = new RootStore()

function App() {
  return (
    <RootStoreProvider store={rootStore}>
      <TodoListView />
      <Counter />
    </RootStoreProvider>
  )
}

export default App
// 消费 RootStore 的其他组件
import { useRootStore } from "../../stores/RootStore"

function TodoHeader() {
  const { todoListStore } = useRootStore()
}

function Counter() {
  const { counterStore } = useRootStore()
}

4. 数据监测

4.1 autorun 方法
  1. 监控数据变化执行副作用,接收一个函数作为参数,参数函数用来执行副作用,当参数函数内部使用的 observable state, computed 发生变化时函数会运行,初始运行 autorun 方法时参数函数也会运行一次。

    import { autorun } from "mobx"
    import { useEffect } from "react"
    
    function Counter() {
      const { counterStore } = useRootStore()
      useEffect(() => {
        // 确保 autorun 方法只被初始化一次
        autorun(() => {
          console.log(counterStore.count)
        })
      }, [])
    }
    
  2. 对于基本数据类型,属于值传递,mobx 只能跟踪到原始属性,跟踪不到复制后的值。

    useEffect(() => {
        let count = counterStore.count
        autorun(() => {
          // 错误写法, mobx 跟踪不到变量 count
          console.log(count)
        })
      }, [])
    
  3. 对于引用数据类型,只要引用地址不发生变化,mobx 就可以进行跟踪

    // CounterStore.js
    class CounterStore {
      person = { name: "张三" }
    }
    
    // Counter 组件
    function Counter() {
      const { counterStore } = useRootStore()
      useEffect(() => {
        const person = counterStore.person
        autorun(() => {
          console.log(person.name)
        })
      }, [])
      return (
        <div>
          <p>{counterStore.person.name}</p>
          <button onClick={() => runInAction(() => (counterStore.person.name = "李四"))}>李四</button>
          <button onClick={() => runInAction(() => (counterStore.person = { name: "王五" }))}>王五</button>
        </div>
      )
    }
    
  4. 去除控制台中和 useEffect 相关的 ⚠️

    在 react 17 版本中,官方团队修改了脚手架工具,允许直接在外部声明 .eslintrc 文件覆盖 eslint 配置,不需要使用 package.json, react-app-rewired 和 customize-cra 就可用实现 eslint 配置

    在项目的根目录下新建 .eslintrc.js 文件并加入如下内容

    module.exports = {
      plugins: ["react-hooks"],
      rules: {
        "react-hooks/exhaustive-deps": 0
      }
    }
    

    注意此处不需要下载 eslint-plugin-react-hooks 插件,create-react-app 脚手架工具内部已经内置。

    配置完成后重新启动应用。

4.2 reaction 方法

监控状态变化执行副作用,接收两个函数作为参数,第一个函数返回要监控的状态,第二个函数用来执行副作用,只有当第一个函数返回的状态发生变化时,第二个函数才会执行。reaction 方法提供了更加细颗粒度的状态控制。

和 autorun 不同,reaction 初始时不会执行副作用。

import { reaction } from "mobx"

function Counter() {
	useEffect(() => {
  	reaction(
      () => counterStore.count,
      (current, previous) => {
        console.log(current)
        console.log(previous)
      }
    )
  }, []) 
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
React前端工程化是指在React项目开发过程中,通过一系列的工具和规范来提高开发效率、代码质量和项目可维护性的一种方法。下面是React前端工程化的几个重要方面: 1. 代码组织:合理的组织项目代码结构,可以按照功能或模块进行划分,使得代码易于维护和扩展。 2. 模块化开发:使用ES6的模块化语法,将项目拆分为多个独立的模块,每个模块负责特定的功能,提高代码的可复用性和可维护性。 3. 构建工具:使用构建工具(如Webpack、Parcel等)来自动化构建过程,包括代码编译、打包、压缩等,提高开发效率。 4. 组件化开发:将页面拆分为多个可复用的组件,每个组件负责特定的功能,通过组合不同的组件来构建页面,提高代码的可维护性和可复用性。 5. 状态管理:使用状态管理库(如Redux、Mobx等)来管理应用的状态,使得状态变更可追踪、可预测,方便进行状态共享和数据流控制。 6. 自动化测试:使用自动化测试工具(如Jest、Enzyme等)编写单元测试、集成测试和端到端测试,保证代码质量和功能的稳定性。 7. 代码规范:使用代码规范工具(如ESLint、Prettier等)对代码进行静态检查和格式化,统一团队的代码风格,提高代码质量和可读性。 8. 持续集成与部署:使用持续集成工具(如Jenkins、Travis CI等)将代码自动构建、测试和部署到服务器,实现快速迭代和持续交付。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值