V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
mouyase
V2EX  ›  问与答

ReactNative 开发时,如何设计一个数据“灵活”的组件呢?

  •  
  •   mouyase · 262 天前 · 1228 次点击
    这是一个创建于 262 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在项目中遇到了类似这样的需求。

    有一个类似表单的页面,这个页面里的每一个组件的数据源都可能会有以下几种情况。

    • 当前页面没有任何操作时的初始值
    • 当前页面从外部跳转过来时,使用外部跳转过来的值
    • 当前页面编辑后,缓存到本地的值
    • 页面里的交互逻辑,比如操作了 A 组件导致 B 组件变化了的值

    基本的逻辑就是页面里总是显示最后一个激活的值,但是这种情况下不知道如何设计组件才能达到一个比较好的效果了。

    之前试着把数据放进了 ref ,但是这样数据改变又很难刷新,不知道有没有什么好办法来处理呢。

    23 条回复    2024-04-08 20:44:28 +08:00
    rabbbit
        1
    rabbbit  
       262 天前
    没太看懂,要我写就是
    1 独立的表单组件,可以传值进来,用户编辑后抛出事件,别的值从哪来、存到哪一律不管。
    2 取值、缓存值这些操作独立出来放外面,根据情况看封装个 hook 还是塞到状态管理里啥的。
    mouyase
        2
    mouyase  
    OP
       262 天前
    @rabbbit

    如果传值进来的话,是通过 props 传进来吗?那这个值是不是需要是一个 state ?那我修改 state 的时候,会导致这个组件和父级组件都重新渲染,会导致重复的生命周期。
    事件传出去的值是不是也要用 state 保存呢?不然应该如何根据一个组件的值修改另一个组件呢。
    huijiewei
        3
    huijiewei  
       262 天前
    React 的组件只有 2 种值

    1. 外部传入 props 或者 useContext 的跨组件值,这些都是外部值
    2. 内部变化 state

    你所说的所有情况都可以抽象为这种

    当前页面没有任何操作时的初始值
    当前页面从外部跳转过来时,使用外部跳转过来的值
    -----
    上面两种情况是一样的,都是初始值,放在 props 传入


    当前页面编辑后,缓存到本地的值
    -----
    其实就是内部 state 的缓存,这里的缓存看你控制的细粒度了,可以整个表单的 state 缓存,也可以单独组件的 state 缓存,这里抽象会复杂一些,性能要求不高,表单控件不多的话,就用表单缓存


    页面里的交互逻辑,比如操作了 A 组件导致 B 组件变化了的值
    -----
    A 引起 B 变化就是 B 的 props 引入了根据 A 变化的值,这种情况下 B 肯定要重新渲染的


    React 的特性需要抽象成最小细粒的组件,以组件为单位渲染即可。

    建议使用现成的 Form 组件或者 react-hook-form ,UI 根据数据渲染即可
    rabbbit
        4
    rabbbit  
       262 天前
    大概这个意思,input 组件啥都不管

    import React,{ useState } from 'react';

    function MyInput(props) {
    return (
    <input value={props.value} onInput={(e) => {
    props.onInput(e.target.value)
    }} />
    )
    }
    export function App(props) {
    const [inputValue, setInputValue] = useState('1')
    return (
    <div className='App'>
    <MyInput value={inputValue} onInput={(value) => {
    setInputValue(value)
    }}/>
    </div>
    );
    }
    mouyase
        5
    mouyase  
    OP
       262 天前
    @huijiewei 感谢解惑。

    如果是操作 A 组件导致 B 组件变换。
    <Comp>
    <A />
    <B />
    <C />
    <D />
    <E />
    <F />
    </Comp>
    如果是这种组件结构的话,A 去修改 B 的 props 值,则需要把 state 放到 Comp 里吧。但是如果是这样,state 就会导致整个 Comp 都会重新渲染,最后导致 CDEF 组件全都重渲染,这种情况有什么好的办法吗。
    mouyase
        6
    mouyase  
    OP
       262 天前
    @rabbbit 这种情况下,MyInput 每次输入都会导致 App 组件整体重渲染吧。
    rabbbit
        7
    rabbbit  
       262 天前
    其实没有性能压力表单啥的重新渲染也就渲染了
    rabbbit
        8
    rabbbit  
       262 天前
    有性能压力的话我就不太清楚了,我这用 vue ,react 写的少。
    有性能要求的时候我一般都是直接在 vue 里写原生 js 操作 DOM 或者用 lit 这种 web component 这种。
    rabbbit
        9
    rabbbit  
       262 天前
    也许 useMemo 能解决你说的子组件被更新的问题
    参考一下这个看看?
    https://www.v2ex.com/t/854260
    wuyiccc
        10
    wuyiccc  
       262 天前
    用 useMemo 控制一下防止组件重复渲染?
    wkmike
        11
    wkmike  
       262 天前
    @mouyase #5 ABCDEF 都用 React.memo 包一下,各自定制 Props 变更判断
    mouyase
        12
    mouyase  
    OP
       262 天前
    @wkmike 那是不是所有的组件的 props 都要独立成不同的 state 呢,如果所有的 state 都在同一个 Object 里,是不是就无效了?
    wkmike
        13
    wkmike  
       261 天前
    @mouyase #12 如果都是外部组件并且接收同样的 Props 比如你说的 state 对象,A 组件根据 state.a 来更新,那你在 React.memo 第二个参数里面只判断 prevProps.state.a 和 nextProps.state.a 是否变化就行了
    mouyase
        14
    mouyase  
    OP
       261 天前
    @wkmike 是不是只有类组件才能这样处理,如果是函数组件呢
    wkmike
        15
    wkmike  
       261 天前
    @mouyase #14 React.memo 就是给 Function Component 实现了类似 Class Component 生命周期 shouldComponentUpdate 方法用来优化性能的高阶组件
    rabbbit
        16
    rabbbit  
       261 天前
    试着研究了一下,这样倒是不会更新,不过感觉好麻烦

    import React,{ useState, memo } from 'react';

    function MyInput(props) {
    return (
    <input value={props.value} onInput={(e) => {
    props.onInput(e.target.value)
    }} />
    )
    }

    function MyInputB(props) {
    return (
    <input value={props.value} onInput={(e) => {
    props.onInput(e.target.value)
    }} />
    )
    }

    const InputBMemo = memo(({value, onInput}) => {
    return <MyInputB value={value} onInput={onInput}/>
    },(oldProps, newProps) => oldProps.value === newProps.value)

    export function App(props) {
    const [inputValue, setInputValue] = useState({
    a: 1,
    b: 2
    })

    return (
    <div className='App'>
    <MyInput value={inputValue.a} onInput={(value) => {
    setInputValue({
    ...inputValue,
    a: value
    })
    }}/>

    <InputBMemo value={inputValue.b} onInput={(value) => {
    setInputValue({
    ...inputValue,
    b: value
    })
    }}/>
    </div>
    );
    }
    wkmike
        17
    wkmike  
       261 天前   ❤️ 1
    @rabbbit #16 那就组件内用 useMemo 咯,只引用各自用到的 a 和 b ,另外 setInputValue 直接用 prevState

    https://gist.github.com/wkmike/e3787e498dd7288e990a96d2cfe5ec3b
    Mandmg
        18
    Mandmg  
       261 天前
    用 redux 啊..
    直接解决了
    huijiewei
        19
    huijiewei  
       261 天前
    @mouyase C D E F 并不会重新渲染,根组件的渲染不会影响子组件,除非你也改变了子组件的 props ,这就是 React 为啥要细粒度组件的原因
    br_wang
        20
    br_wang  
       261 天前
    参考下 ElementUI 或 AntDesign 里 Form 组件的设计,一般内部还是会维护一个 state ,处理编辑的状态记录,还会有类似 isDirty 的 flag 用于标注该项是否由用户编辑过,编辑过就要处理校验等等。
    mouyase
        21
    mouyase  
    OP
       261 天前 via Android
    @huijiewei 刚刚这边做着做着就有了一个疑问。

    假设还是上面的结构。

    A 组件内部有一个 state ,一个按钮和一个文本,state 用来判断一个文本的展示与否。同时我需要外面(比如 B 组件)接收到这个 state 用于逻辑处理。所以我应该在按钮点击事件的时候,修改 state ,同时调用父组件传入的 onChange 函数,传出 state 。

    但是如果这种情况下,我还需要 C 按钮也可以切换这个 A 组件的 state ,我应该怎么处理比较好?

    我现在的做法是,在父组件放一个 state ,然后 A 组件 onChange 的时候,将传出的数值赋值到这个 state 上,同时,又把这个 state 传入 A 组件作为 prop ,然后在 a 组件用 useEffect ,当这个值变化的时候再给 A 组件内的 state 赋值。然后 C 操作的时候修改父级的 state 。

    这样似乎会导致 A 组件内部按钮按下时,组件渲染两次。
    huijiewei
        22
    huijiewei  
       261 天前
    @mouyase 开发的时候使用了 StrictMode ,都会渲染两次。
    mouyase
        23
    mouyase  
    OP
       261 天前
    @huijiewei 不是严格模式的问题,state 被连续赋值了两次
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2638 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 15:34 · PVG 23:34 · LAX 07:34 · JFK 10:34
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.