细节齐全的 SOA —— hooks 组件或可尝试

SOA —— 面向服务架构 是一种组件封装方式,本质上是对 功能单元的拆分,这一点与传统组件只拆分视图和内部逻辑(无法高效调用外部状态逻辑)的方式不同

而 React 如果要想实现 SOA,就需要从 Context 这一结构入手,因为控制反转(依赖注入)是实现高效 SOA 的前提

React 的 Context 直接说是依赖注入不严谨,因为依赖注入很多时候是面向对象的概念,而 React 的语境很多都是基于函数式的,这一点其实应该批评 React,一方面放 useRef 出来,搞很重数据驱动,一方面又提倡函数式,这样会给社区的学习生产带来很大的误导,不过矫枉必须过正,教育社区的作用也是很大的

首先,我们要明白一个非常重要的概念 —— 状态逻辑 (stateful logic)

英文原文知道,这个词中,状态逻辑两个词不是并列关系,完整表述应该是 —— “有状态的逻辑”,即 状态 + 状态化事件 + 响应式流(effect -> setState -> effect …)

状态管理 -> 状态逻辑管理

我们来看看 React 官方的表述:

截屏2021-06-01 上午10.47.28.png

截屏2021-06-01 上午10.50.32.png

注意,在 Hooks 语境下,不止状态可以方便地进行传递,整个逻辑也可以

那怎么才能让逻辑也畅通无阻地进行传递呢?(不依赖丑陋的高阶组件和本不应该用在此处的render props)

Context 提供了无需层层传递即可进行状态分发的方案,配合其他 hooks api,可以有效解决组件无法高效调用外部状态逻辑的缺陷

Context + useContext

// 声明上下文
const SomeLogicContext = createContext({a:'',b:'',()=>{}})

// 声明服务(共享热插拔逻辑代码)
function SomeLogic(propA,propB){
   const [a,setA] = useState('')
   const [b,setB] = useState('')
   useEffect(()=>{
     setA(propA)
   },[propA])
   useEffect(()=>{
     setB(propB)
   },[propB])
   const setAB = useCallback((_a,_b)=>{
      setA(_a)
      setB(_b)
   },[])
   return {
     a,
     b,
     setAB
   }
}

// 声明Provider
function SomeComponent(){
  // 初始化
  const someLogic = useSomeLogic('','')
  // provide
  return <SomeLogicContext.Provider value={someLogic}>
    /** 子孙组件共享 someLogic **/
  </SomeLogicContext.Provider>
}

// 子孙组件使用
function SomeChild(){
  const someLogic = useContext(SomeLogicContext)
  // ...
}
复制代码

这样,你就不需要再使用复杂丑陋且功能缺失的方案来将逻辑跨组件了,真正实现了高内低耦

然而,这是很基础的部分,讨论远没有真正结束,因为这是函数式SOA方案,并非面向对象中 —— 我拿到实例化对象引用了!我实现了 控制反转!我实现了 SOA!—— 这么简单

不变的 Context

Context 的行为和 Props 非常相似,比如返回结果如果是对象的话,会进行 Objectis 比对,保证不变性

表面看上去没什么问题,但是,如果你在注入部分试用了 setAB 函数作为参数(谁会傻到有 useContext 还用 props 传函数?因为社区第三方组件不是用 context 写得,所以你避免不了传 props)

 <SomeLogicContext.Provider value={someLogic}>
    /** 子孙组件共享 someLogic **/
    /** 对不起,虽然你只有无依赖 callback setAB **/
    /** 但是只要 someLogic 返回值有一个变化,你都得重绘vm(重新调用 SomeChild 组件函数) **/
    <SomeChild onChange={setAB}/>
  </SomeLogicContext.Provider>
复制代码

这就是 React 官方所说的 —— 避免向下传递回调

不可能!绝对不可能!既然已经给了状态逻辑复用,不向下传递回调还玩个屁!

React 给的方案,是用 useReducer + dispatch,state 分开 provide,且不说丑陋不丑陋,useReducer 根本就不应该用在这种地方

出现这种问题,其实也是 React 想要给使用者减负,原因很简单,它将 context,props,state 变化,一定会返回新的 jsx 当做 React 组件的默认逻辑

不过应该这样看么?

新版函数式组件,只要 props,context 发生改变,就会重新运行函数,这个逻辑无可厚非,但是相关变量不加控制,直接放置在 jsx 中,并直接返回, 是函数式组件的默认行为么?

不是!

既然承认不加 useMemo 或者 effect 新 state ,变更得不到约束,甚至不惰性初始化,性能将会被拖累

为什么会想当然认为 jsx 应该直接返回,子组件 props 的参数应该直接传递呢?

 <SomeLogicContext.Provider value={someLogic}>
    /** 子孙组件共享 someLogic **/
    { useMemo(()=>(<SomeChild onChange={setAB}/>),[setAB])
  </SomeLogicContext.Provider>
复制代码

写在 return 里面的东西,和写在函数体里面的东西,在调度方面,并没有什么区别

所谓不要把 useMemo 当成语义上的保证的意思是,在依赖项改变才进行计算这句话的意思不对,useMemo 只是在 memorzied state 上添加了一个新值,并让这个新值跟随 state 的调度进行调度而已(依赖被传递),至于这个调度到底是不是同步一致的,react 现在也不保证,并不影响你用在调度处理这种地方

也就是说,useMemo 并不是性能优化 api,而是个调度控制 api(没有性能需要你优化,而是 context,props state 改变必刷新这个逻辑有问题)

但是,聪明的朋友们马上反应过来,react 默认 组件返回值刷新频率的原因在哪里了

可读性! —— 是高内低耦了,但是 jsx 不是稀碎了么?

没错,但是出现这个问题的原因是什么?

函数式组件新身份

函数式组件只是提供 jsx 式视图调用接口

换句话说,react 应用中的组件,只是视图,而 hooks 状态逻辑,才是真组件(功能单元),类似这种:

// 在这里进行组装
function ViewComponent(){
    const controlButton = useControlButton()
    const childCompo = useChildCompo()

    return <div>
      {controlButton}
      {childCompo}
      /** 其他纯组件 **/
      <Pure/>
    <div/>
}

// 组件视图也是 hooks
function useControlButton(){
   const [label,setLabel] = useState('button lable')
   return useMemo(()=>(<button>{label}</button>),[label])
}
复制代码

也就是说,将只有一层的组件视图,做成树形结构的组件视图,这样的组件,才是真组件

这种做法还有优势,比如 Provider 可以直接放置在 hooks 中进行绑定,功能单元可以自己形成联系,而不必等到 函数式组件组装 的时候,才建立逻辑关系,这样可以提高抽象开发的层次,没错,为下一个架构技术做铺垫

熟悉 Angular 的朋友应该很容易发现,这个做法是 module 的基础,即 函数式组件 —— declaration, hooks中的 memo jsx —— 动态组件,hooks 中 jsx 使用的组件 —— 私有组件,hooks —— 服务,Context.Proider —— 声明提供商

useMemo jsx 负责动态加载视图,jsx 中使用的组件 也不会被轻易暴露给外部,最后统一由 最外层组件进行组装并展示,所有视图,状态,逻辑,尽皆热插拔,性能全部无需担心,理论最优(hooks 版本只有调度问题,没有性能问题)

这样的结构,就实现了细节齐全的 SOA,实现了完整的 高内低耦

在加上前文提到的,不同调度系统混入,采用 useReducer 补齐短板,整个 SOA 用起来就一个词 —— 舒服

但是似乎还有个问题

除了高内低耦,组件角色转换带来的封装性,hooks 本身的状态逻辑封装复用,在加上 useReducer ,useMemo 带来的调度处理能力,整个架构看上去很强大,不过总感觉缺了点什么?

因为目前为止,这些 api 和用法,全是自底向上的,而应用开发,是自顶向下的

所以,必须跟进一步,用强大的架构理论,将所有这些底层技术的优势发挥出来!

我相信你已经能想到什么了

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享