V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
DreamTrace
V2EX  ›  React

关于 react 编程思路的问题

  •  1
     
  •   DreamTrace · 2021-04-26 21:11:19 +08:00 · 3769 次点击
    这是一个创建于 1342 天前的主题,其中的信息可能已经有所发展或是发生改变。

    萌新,最近看了一下react,感觉它很依赖于状态管理?不知道是不是因为JSX语法的原因,函数 /类组件只能在<ComponentName />中传入相应props,如果父组件要访问子组件的方法就必须传入一个类似SetHandler()的方法把子组件的方法绑定到父组件的state上,而这样不就相当于把子组件的statefunction移到父组件上吗?似乎没有类似SubComponentName.funName()的访问方式...有点迷茫,可能说的不是很清除,不知道大家是怎么解决这个问题的?

    第 1 条附言  ·  2021-04-27 23:59:34 +08:00

    第一次提问竟然有这么多回复,总之感谢大家,可能是我有点思维定势了吧,过去编程一般我会在Controller读取设置并将它或它的一部分传递给相应Component,然后调对应Componentfunction来解决问题,所以我觉得单向的数据流没有什么问题,问题是react中的数据流是大概率会被改动的,这就需要一些状态逻辑,而SubComponent中状态逻辑的方法基本只能通过它自己JSX中事件的回调,换言之这些方法调用的位置被限定死了,所以我有点懵,在想这是不是故意设计来规避某些写法来的。

    具体问题是我在用@material-uiSnackbar,因为它的设置比较多,所以我稍稍封装了一下,这样就相当于有一部分状态在SubComponent中,而SubComponent本身又要暴露一部分状态在FatherComponent中,比如使用open来进行开关,然后我写了下面错误代码:

    //SubComponent
    function SnackBar(props) {
      const [state, setState] = React.useState({
        anchorOrigin: props.state.anchorOrigin || { horizontal: 'right', vertical: 'top' },
        open: props.state.open || false,
        Transition: props.state.Transition || Slide,
      });
        
      const handleClose = () => {
        setState({
          ...state,
          open: false,
        });
      };
    
      return (
        <Snackbar
          open={state.open}
          onClose={handleClose}
          autoHideDuration={3000}
          key={state.Transition.name}
          >
          <Alert onClose={handleClose} severity="success">
            This is a success message!
          </Alert>
        </Snackbar>
      );
    }
    
    //FatherComponent
    function FatherComponent(props) {
      // ...other code
      return (
        // ...other code
        <SnackBar state={state}></SnackBar>
      );
    }
    

    毫无疑问没有反应,因为这样SubComponentstate不会被更新,当时就想要是可以直接外部调用SubComponent的方法设置open就好...后来发现更新props也算副作用来的,需要用useEffect来更新,如果是class component应该是在componentWillUpdate加入setState的逻辑。

    如果一定要在父组件调用子组件的方法,可能就是ref了,感谢大佬们~

    30 条回复    2021-04-28 19:10:33 +08:00
    Jirajine
        1
    Jirajine  
       2021-04-26 21:29:00 +08:00
    React 的核心思想就在于 f(state) = ui,数据流是单向的,你得先把思维方式转变过来。
    momocraft
        2
    momocraft  
       2021-04-26 21:35:59 +08:00
    像这样手工用 props 连接也是可以

    react 自带的以前有 ref + class component 可能最接近你找的那种

    现在有 useImperativeHandle
    hello2060
        3
    hello2060  
       2021-04-26 21:40:17 +08:00 via iPhone
    后端,只学过 react,没正儿八经用过。

    你是不是把概念理解错了,这里的父组件是更高一级的组件,拥有子组件。相当于一个页面组件拥有 header 组件,拥有 footer 组件

    而不是面向对象里的父子组件是继承关系,这里没有继承关系啊。那父组件为啥要调用子组件的功能?父组件 render 的时候子组件也自动会 render, 这不就够了吗?

    数据是夫传给子的,既然数据在父那,对数据的操作自然也是父负责,所以子只要回调就行。
    love
        4
    love  
       2021-04-26 22:04:01 +08:00
    一定需要调用子组件的内部方法的情况非常少,绝大部分情况还是简单地维护各层 state 就行了,比传统方式简单可靠多了
    JerryCha
        5
    JerryCha  
       2021-04-26 22:19:38 +08:00
    恭喜你,依靠自己悟出了 React 编程的一大重点:状态提升
    beizhedenglong
        6
    beizhedenglong  
       2021-04-26 22:20:39 +08:00
    @Jirajine 你没说到点子上,vue 也是单向的
    across
        7
    across  
       2021-04-26 22:38:33 +08:00
    因为 React 只是个 View 层数据流,在 MVC 视角下,你是想把 VC 都放在 ReactComponent 下···· 这事儿就不应该放 View 层做。
    qiuxuqin
        8
    qiuxuqin  
       2021-04-26 22:40:01 +08:00
    @beizhedenglong vue 的子组件通过 emit 事件,然后父组件接收事件进行处理,这里哪里是单向的了?而且父组件可以直接通过 this.$refs.childComponent 获取到子组件内部的各种变量和方法,这跟单向流动差远了。
    beizhedenglong
        9
    beizhedenglong  
       2021-04-26 22:45:23 +08:00
    @qiuxuqin 按照你这这个逻辑,react 通过 callback 回调传数据和 emit 事件传数据又有啥本质区别?
    agdhole
        10
    agdhole  
       2021-04-27 00:07:40 +08:00
    搜一下 immutable
    Rocketer
        11
    Rocketer  
       2021-04-27 01:33:39 +08:00 via iPhone
    状态管理算是个 hack,初学先别考虑那个。

    React 最重要的基本概念之一就是数据单向流动,只能从父流向子。所以几个组件要想共享数据,就得找个共同的祖先来持有那个数据。

    至于更新状态的函数,你也当数据来用就行了,毕竟 JavaScript 里一切皆对象,字符串和函数一视同仁。
    mongodb
        12
    mongodb  
       2021-04-27 02:06:53 +08:00
    @Rocketer 那会不会不小心容易写出一种倾向,让最高的父干脆全部负责数据处理得了。。。
    Rocketer
        13
    Rocketer  
       2021-04-27 02:29:11 +08:00 via iPhone
    @mongodb 状态管理本质上就是这么个东西啊。所以说初学先别考虑状态管理,等彻底理解了 React 再看。
    seki
        14
    seki  
       2021-04-27 03:28:35 +08:00
    如果一个组件 props 在更新,自己的 state 也在更新,两部分想归到同一个流里面需要思考很多情况,强行写出来性能可能也会比较差

    把组件分离成专门用于显示的内容的,以及专门用来处理数据的会让逻辑更简单,实现单向数据流也会更少心智负担

    再复杂到一定阶段就会用 context 和 redux / recoil / mobx 之类来把数据处理单独抽出来了
    walpurgis
        15
    walpurgis  
       2021-04-27 08:48:02 +08:00 via Android
    https://zh-hans.reactjs.org/docs/lifting-state-up.html
    数据和逻辑提到父组件是官方推荐做法
    gouflv
        16
    gouflv  
       2021-04-27 09:06:20 +08:00 via iPhone
    1. 如果子组件本身足够复杂,外部可以通过注入类似服务的对象,由服务来间接控制子组件,参考 antd 的 useForm
    2. 简单的子组件,用 useImperativeHandle 暴露 api
    jguo
        17
    jguo  
       2021-04-27 09:08:05 +08:00
    父组件调用子组件的方法意味着强耦合,不符合 react 的思路
    forsigner
        18
    forsigner  
       2021-04-27 10:05:38 +08:00
    可以开始了解 React 之状态管理大乱斗了
    shunia
        19
    shunia  
       2021-04-27 10:20:40 +08:00   ❤️ 1
    这和状态管理有啥关系。。。你只要把 <Component /> 理解成 function Component() {} 就好了。
    DOLLOR
        20
    DOLLOR  
       2021-04-27 10:39:58 +08:00
    父组件没必要去访问子组件内部的状态。
    就像你调用函数的时候,函数外部没必要去获取函数内部的局部变量。
    ccraohng
        21
    ccraohng  
       2021-04-27 10:45:15 +08:00   ❤️ 1
    只关心组件自己的状态维护,如果你只是改变子组件的数据,可以改成受控组件。比较复杂的函数操作,比如 `slider.next()`,通过 ref 暴露就行。
    zhaol
        22
    zhaol  
       2021-04-27 11:39:14 +08:00
    // 子组件
    class ComponentName {

    componentDidMount() {
    this.props.onReady && this.props.onReady(this)
    }

    aaa = () => {}
    }

    //父组件
    class Father {
    subComponentName = null

    componentDidMount() {
    this.subComponentName.aaa()
    }

    render() {
    return <ComponentName onReady={(instance)=>{this.subComponentName = instance}}/>
    }
    }
    zhaol
        23
    zhaol  
       2021-04-27 11:41:22 +08:00
    @zhaol 不建议这样做,但是有时候业务很麻烦,这样做确实会方便.
    towry
        24
    towry  
       2021-04-27 14:52:02 +08:00
    看看 react 的 ref 是怎么实现的。
    DreamTrace
        25
    DreamTrace  
    OP
       2021-04-28 00:04:06 +08:00
    @zhaol 没有看懂......`onReady`似乎是`props`上的一个变量,我应该看哪里?
    zhaol
        26
    zhaol  
       2021-04-28 10:13:32 +08:00
    @DreamTrace 就是 ref 的手动实现,子组件通过 onready 把组件实例抛到父组件用一个变量(subComponentName)保存,这样你就可以通过这个变量调用子组件里面的属性和方法了
    qiuxuqin
        27
    qiuxuqin  
       2021-04-28 11:24:48 +08:00
    @beizhedenglong react 的数据变更函数是通过父组件传到子组件的,在子组件调用 setState 函数实际上是调用父组件的方法,更改父组件的数据,然后传给子组件的那个数据也跟着变了,所以 React 实际上还是数据单向传送的。vue 的 emit 是在子组件通过事件通知父组件,它这个数据变更是通过子组件往上传的,所以是双向流动。
    qiuxuqin
        28
    qiuxuqin  
       2021-04-28 12:04:11 +08:00
    @beizhedenglong 可能我这个解释也不一定准确。我再解释一下:vue 的 v-model 语法其实就算是双向绑定,虽然说它是$props.value 和 emit('change', value )的语法糖。另外如果某个 prop 是个对象,是可以直接修改这个对象的 key 的,这样父组件和子组件也可以的这个对象值也会改变,视图也会更新(当然如果是原始值会报错)。
    beizhedenglong
        29
    beizhedenglong  
       2021-04-28 12:24:23 +08:00 via iPhone
    @qiuxuqin VUE 实现导致的,它不建议你直接改,react 你也可以直接改
    qiuxuqin
        30
    qiuxuqin  
       2021-04-28 19:10:33 +08:00
    @beizhedenglong vue 修改 prop 对象内部的值,它还是响应式的,视图会更新。react 直接修改 prop,视图并不会更新,要用 setState 才会触发视图更新。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2497 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 15:39 · PVG 23:39 · LAX 07:39 · JFK 10:39
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.