npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

weave-state

v0.3.1

Published

将状态放置在组件之外,自由编织它们。

Downloads

2

Readme

weave-state

将状态放置在组件之外,自由编织它们。

weave-state 虽然有 React Hook 预设,但是它本身并不依赖某个特定的前端框架/库。

此状态库受到 Jotaizustand 的启发,感谢这些优秀的开源项目。

安装

# 使用 pnpm 进行安装
pnpm add weave-state

# 使用 yarn 进行安装
yarn add weave-state

# 使用 npm 进行安装
npm i weave-state

基本用法

基本类型值

import create from 'weave-state'

// 创建一个状态
const countState  = create(0)
// 使用函数创建
const countState2 = create(() => 0)

// 获取状态
console.log(countState.getState())

// 监听状态发生变化
countState.addListener((state) => {
  console.log('状态发生变化:', state)
})

// 更新状态。调用后将触发 listener
countState.setState(1)
// 使用函数可获取最新的状态值
countState.setState((val) => val + 1)

引用类型值

import create from 'weave-state'

const store = create({ name: 'Andrew', age: 14 })

store.addListener((state) => {
  console.log('数据发生变化:', state.age)
})

// 注意:即使没有修改 age, listener 依然会被触发。
store.setState((prevState) => ({ ...prevState, name: 'Helen' }))

指定 state 的 listener

在上面的例子中可以看到,只要调用 setState 并修改数据后,便会触发 listener。

不过,侦听更加细粒度的状态有时候会很有用,此时可以使用 selector

import create from 'weave-state'
import { selector } from 'weave-state/extend'

// 使用 selector 来对 store 对象进行扩展
const store = create({ name: 'Andrew', age: 14 }).use(selector())

store
  .selector((state) => state.age)
  .addListener((state) => {
    console.log('age 发生变化:', state.age)
  })

// 修改 age, 将触发 listener
store.setState((prevState) => ({ ...prevState, age: prevState.age + 1 }))

// 修改 name 不会触发 listener
store.setState((prevState) => ({ ...prevState, name: 'Helen' }))

派生状态 computed

有时候,你需要依赖 state 并进行计算,那么这时候你可以考虑使用 computed 来实现:

import create from 'weave-state'
import { computed } from 'weave-state/extend'

const state = create(2)

const doubleValue = computed((read) => {
  return read(state) * 2
})

const user = create({ name: 'Andrew', disabled: true })
const isDisabledUser = computed((read) => {
  // read 函数支持 selector 的特性
  return read(user, (val) => val.disabled)
})

computed 会缓存上一次的计算结果。

它在创建之初便立即运行一次,在此之后,只有它所依赖的值发生变化后才会重新执行。

可写的派生状态

当你从一个复杂状态中遴选一个特定的值作为一个独立的状态后,若你需要更新它,便会发现比较麻烦,并且在语义上还不够友好。

此时你可以传入一个包含名为 get, set 函数的对象,独立配置数据读取与修改时的行为。

import create from 'weave-state'
import { computed } from 'weave-state/extend'

const state = create({ value: 0 })

const value = computed({
  get(read) {
    return read(state, (val) => val.value)
  },
  set(val) {
    state.setState((prevState) => ({...prevState, value: val}))
  },
})

value.setState((val) => val + 1)

computed 中的 read 是什么?

listener

设置 key 值

携带 key 值的 listener 拥有优先调用权。

import create from 'weave-state'

const store = create({ name: 'Andrew', age: 14 })
const KEY = 'YOUR_STRING_KEY'

store.addListener((state) => {
  console.log(state)
  console.log('即使声明在前,但是稍后调用')
})

store.addListener(() => {
  console.log('拥有优先调用权')
}, KEY)

移除 listener

const store = create({ name: 'Andrew', age: 14 })

// addListener 返回值便是该 listener 的移除函数
const removeFn = store.addListener(() => {
  console.log('listener call')
})

const KEY = 'YOUR_STRING_KEY'
store.addListener(() => {
  console.log('listener2 call')
}, KEY)

store.removeListener(KEY) // 指定 key 值移除

store.clearAllListener() // 清空所有 listener

API 签名及解释

参数

import create from 'weave-state'

// 初始化默认值
create(0)
create(() => 0) // 支持使用函数创建

// 参数 2 接收一个函数,用于在 setState 时比较新值与旧值是否相等,
// 若两值相等,则跳过触发 `listener`。
create(0, Object.is)

getState

获取当前最新的状态值。

setState

函数签名如下:

 type SetStateAction<S> = S | ((prevState: S) => S)
 type SetStateFn<S> = (action: SetStateAction<S>) => void

setState 接收 1 个参数:

可以为新的值或者函数调用。

const store = create(false)

store.setState(true) // 新值
// 或者
store.setState((currentState) => !currentState) // 函数调用

addListener

添加一个侦听器,当数据变化时则会触发。

函数签名:

type RemoveListenerFn = () => void
type ListenerKey = string | symbol
type Listener<T> = (state: T, prevState: T) => void

function addListener: (listener: Listener<S>, key?: ListenerKey ) => RemoveListenerFn

它接收两个参数:

  1. 参数 1 listener

    侦听器函数,数据变化后调用该函数。

  2. 参数 2 key

    该参数为可选参数,用于标记并保持唯一。确保侦听器不会重复添加。

    并且,拥有 key 值的侦听器总是在匿名侦听器前调用。

函数返回值:

addListener 返回一个函数,可用于销毁此侦听器。

removeListener

根据传入的 key 销毁侦听器。

clearAllListener

销毁所有的侦听器。

notify

手动触发所有 listener。

use

该函数可用于扩展 state 对象的功能,将它当做“插件”理解就好。

它接收一个函数,并将函数的返回值返回,作为新的 api 使用。

这是一个例子:

import create from 'weave-state'

const store = create(2).use((api) => {
  return {
    ...api, // 将原始 api 合并
    getDoubleValue() { // 新增一个获取 2 倍数值的函数
      return api.getState() * 2
    },
  }
})

console.log(store.getState()) // 2
console.log(store.getDoubleValue()) // 4

use 函数的返回值是没有任何约束的,你可以随心所欲创造任何功能。

selector

selector 能够帮助你在复杂状态指定值进行监听。

import create 'weave-state'
import { selector } from 'weave-state/extend'

// 通过 use 挂载
const state = create({ name: 'Andrew', age: 14 }).use(selector())

// 单独使用
const withSelector = selector()
const state = withSelector(create({ name: 'Andrew', age: 14 }))

selector 是一个函数,它有一个可选参数

  • equalityFn 用于在触发 listener 前进行比较新旧值是否相等,若两值相等,则不会触发 listener。 默认使用 Object.is 进行比较。

在 React 中使用

我们创建了一些 hook,方便在 React 中使用。

useWeaveState

用于在 React 中读取状态并更新的 hook,它的使用方法和 React.useState 一致。

通过 use 挂载
import create from 'weave-state'
import { useWeaveState } from 'weave-state/react-extend'

const store = create({ value: 0, age: 0 }).use(useWeaveState.install)

const Example () => {
  const [state, setState] = state.useWeaveState()
  return <></>
}
直接使用
import create from 'weave-state'
import { useWeaveState } from 'weave-state/react-extend'

const store = create({ value: 0, age: 0 })

const Example () => {
  const [state, setState] = useWeaveState(state)
  return <></>
}

useSelector

借助 selector 我们可以侦听指定的 state。

import create from 'weave-state'
import { selector } from 'weave-state/extend'
import { useSelector } from 'weave-state/react-extend'

const store = create({ value: 0, age: 0 }).use(selector()).use(useSelector)

const Example () => {
  const value = state.useSelector(val => val.value)
	const doubleValue = state.useSelector(val => val.value * 2)

  return <></>
}

useValue

仅读取 state,而不需要 update 函数。而且它还支持异步状态。

通过 use 挂载
import create from 'weave-state'
import { useValue } from 'weave-state/react-extend'

const store = create({ value: 0, age: 0 }).use(useValue.install)

const Example () => {
	const state = state.useValue()
  return <></>
}
直接使用
import create from 'weave-state'
import { useValue } from 'weave-state/react-extend'

const store = create({ value: 0, age: 0 })

const Example () => {
	const state = useValue(state)
  return <></>
}

异步 computed

computed 派生状态支持返回一个 Promise。你可以直接使用 useValue 来获取数据,不过需要注意:必须在外层使用 Suspense 组件。

import create from 'weave-state'
import { computed } from 'weave-state/extend'
import { useValue } from 'weave-state/react-extend'

const count = create(1)

const asyncAtom = computed((read) => {
  return new Promise<number>((resolve) => {
    setTimeout(() => {
      resolve(read(count) * 10)
    }, 1000)
  })
})

function Demo() {
  const value = useValue(asyncAtom)
  return <div>{value}</div>
}

function App() {
  return (
    <Suspense fallback={<div>loading..</div>}>
      <Demo />
    </Suspense>
  )
}

weave-state 是如何运行的?

weave-state 是一个非常简单的状态管理库。它的核心是“发布订阅”机制,便是我们看到的 addListener 函数。

当一个状态被创建时,便会在函数内部维护一个 listener 数组。每次调用 addListener 时,便会将 listener 记录在其中。而调用 setState 更新状态时,则调用数组内所有的 listener。

这就是它的实现,非常简单。

为什么读取状态是使用 getState?

这其实是为了获取最新的基本类型值。

在设计 weave-state 时,为了与 setState 函数相对应,所以才有了 getState 函数。并且它也解决了获取基本类型值的问题。

还有其他的解决方案,例如 Vue 3 中的 ref 函数支持声明基本类型值,而基本类型值无法做到响应式,所以 Vue 将其包装在一个拥有名称为 value 的响应式对象中。

computed 中的 read 是什么?

weave-state 是通过“发布订阅”机制来实现的。而 computed 作为派生状态,并在它所依赖的状态发生变化后重新执行,必然需要使用 addListener 来跟踪状态的变化。

所以,read 函数其实是 addListener 的封装,通过 read 函数标记依赖,以此完成依赖收集。所以 computed 才能在依赖更新后重新执行。

read 使用 selector 来支持细粒度的依赖标记。

若你明确派生状态中不需要某些依赖,那你依然可以使用 getState。若非必要,应当避免这种用法。

import create from 'weave-state'
import { computed } from 'weave-state/extend'

const age = create(14)
const name  = create('Andrew')

computed((read) => {
  // 只有 age 被标记为依赖
  return `${name.getState()} is ${read(age)} years old.`
})

通过 read 函数手动标记依赖,即使是异步回调也能正常运行。