先下手为强,在 fre 中疯狂 suspense

1,268 阅读2分钟

halo,大家好,我是 132,今天闲着没事,对 suspense 和 context 进行了封装

因为我一直对尺寸有超高的强迫症,所以 fre core 只提供最最最核心的机制

比如,我提供 promise 的内部捕获,但不提供 Suspense 组件的封装

比如我提供 state ,但不提供 context

但是很多人会质疑,基于现有的 API 和 机制,到底能不能自己封装出来

答案是,of course!

不信,我封装给你看——

Suspense

suspense 的本质就是在外部 throw promise,然后由 fre 内部捕获到,然后打断调和,等 resolve 后,在继续调和

react 目前的 suspense 还不支持数据请求,只支持异步组件

既然 react 不支持,那我就封装一个

export function createSuspense(promise) {
  let pending = true
  let result
  let currentState = null

  return state => {
    if (currentState !== state) {
      pending = true
      currentState = state
    }
    if (pending) {
      throw promise(state).then(res => {
        pending = false
        result = res
      })
    } else {
      return result
    }
  }
}

如上,createSuspense 接收 promise 函数,它返回一个闭包,这个函数负责把 promise throw 出去

它这么用:

function fetchUsers (pageSize) {
  console.log('fetch users from clicli...')
  return fetch(`https://api.clicli.us/users?level=4&page=1&pageSize=${pageSize}`)
    .then(res => res.json())
    .then(data => {
      return data.users
    })
}

const useUser = createSuspense(fetchUsers)

我们将返回的函数作为 hook,可以随意用到任何组件

function App () {
  const [pageSize, setPageSize] = useState(1)
  const users = useUser(pageSize)

  return (
    <main>
      <h1>Fetching clicli users</h1>
      <button onClick={() => setPageSize(pageSize + 1)}>Click {pageSize}</button>
      {users.map(user => (
        <img src={`https://q1.qlogo.cn/g?b=qq&nk=${user.qq}&s=640`} />
      ))}
    </main>
  )
}

这样一来,就可以愉快的使用 suspense 进行数据请求了

目前 react 还没有确定类似的 API,目前的 Suspense 组件是用来控制 lazy 组件的

这类组件的 API,好处是嵌套,坏处就是复用

所以综上所述,我提了这个 rfc:github.com/reactjs/rfc…

如果反响好的话,我就尝试提 pr 试试::>_<::

但是不管怎么说,这个实现是没问题的,至少是 fre ,我能给出的最合适的实现了

Context

结合上面的 Suspense API,我们发现了一个 hooks 封装的常用套路:返回闭包

使用同样的套路,我们可以 20 行封装出 Context API

export function createContext(defaultValue) {
  const listeners = new Set()
  let backupValue = defaultValue

  return () => {
    const [value, setValue] = useState(backupValue)

    useEffect(() => {
      backupValue = value
      listeners.forEach(f => f !== setValue && f(value))
    }, [value])

    useEffect(() => {
      listeners.add(setValue)
      return () => {
        listeners.delete(setValue)
      }
    }, [])

    return [value, setValue]
  }
}

用法是类似的:

const useTheme = createContext('light')

function App() {
  const [theme, setTheme] = useTheme()
  return (
    <div>
      {theme}
      <A />
      <button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
        change
      </button>
    </div>
  )
}

function A() {
  const [theme] = useTheme()
  return <div>{theme}</div>
}

如上,hooks 就是这么灵活,我们可以肆意封装,这些封装未必需要框架内置,用户完全可以自己搞定

总结

以上的两个封装的例子都在 fre 仓库里

github.com/132yse/fre/…

github.com/132yse/fre/…

大家有兴趣可以跑用例,或者去 react rfcs 中一起讨论哈!