萌新,最近看了一下react,感觉它很依赖于状态管理?不知道是不是因为JSX语法的原因,函数 /类组件只能在<ComponentName />中传入相应props,如果父组件要访问子组件的方法就必须传入一个类似SetHandler()的方法把子组件的方法绑定到父组件的state上,而这样不就相当于把子组件的state和function移到父组件上吗?似乎没有类似SubComponentName.funName()的访问方式...有点迷茫,可能说的不是很清除,不知道大家是怎么解决这个问题的?
第一次提问竟然有这么多回复,总之感谢大家,可能是我有点思维定势了吧,过去编程一般我会在Controller读取设置并将它或它的一部分传递给相应Component,然后调对应Component的function来解决问题,所以我觉得单向的数据流没有什么问题,问题是react中的数据流是大概率会被改动的,这就需要一些状态逻辑,而SubComponent中状态逻辑的方法基本只能通过它自己JSX中事件的回调,换言之这些方法调用的位置被限定死了,所以我有点懵,在想这是不是故意设计来规避某些写法来的。
具体问题是我在用@material-ui的Snackbar,因为它的设置比较多,所以我稍稍封装了一下,这样就相当于有一部分状态在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>
  );
}
毫无疑问没有反应,因为这样SubComponent的state不会被更新,当时就想要是可以直接外部调用SubComponent的方法设置open就好...后来发现更新props也算副作用来的,需要用useEffect来更新,如果是class component应该是在componentWillUpdate加入setState的逻辑。
如果一定要在父组件调用子组件的方法,可能就是ref了,感谢大佬们~
|      1Jirajine      2021-04-26 21:29:00 +08:00 React 的核心思想就在于 f(state) = ui,数据流是单向的,你得先把思维方式转变过来。 | 
|  |      2momocraft      2021-04-26 21:35:59 +08:00 像这样手工用 props 连接也是可以 react 自带的以前有 ref + class component 可能最接近你找的那种 现在有 useImperativeHandle | 
|  |      3hello2060      2021-04-26 21:40:17 +08:00 via iPhone 后端,只学过 react,没正儿八经用过。 你是不是把概念理解错了,这里的父组件是更高一级的组件,拥有子组件。相当于一个页面组件拥有 header 组件,拥有 footer 组件 而不是面向对象里的父子组件是继承关系,这里没有继承关系啊。那父组件为啥要调用子组件的功能?父组件 render 的时候子组件也自动会 render, 这不就够了吗? 数据是夫传给子的,既然数据在父那,对数据的操作自然也是父负责,所以子只要回调就行。 | 
|  |      4love      2021-04-26 22:04:01 +08:00 一定需要调用子组件的内部方法的情况非常少,绝大部分情况还是简单地维护各层 state 就行了,比传统方式简单可靠多了 | 
|  |      5JerryCha      2021-04-26 22:19:38 +08:00 恭喜你,依靠自己悟出了 React 编程的一大重点:状态提升 | 
|  |      6beizhedenglong      2021-04-26 22:20:39 +08:00 @Jirajine 你没说到点子上,vue 也是单向的 | 
|      7across      2021-04-26 22:38:33 +08:00 因为 React 只是个 View 层数据流,在 MVC 视角下,你是想把 VC 都放在 ReactComponent 下···· 这事儿就不应该放 View 层做。 | 
|      8qiuxuqin      2021-04-26 22:40:01 +08:00 @beizhedenglong vue 的子组件通过 emit 事件,然后父组件接收事件进行处理,这里哪里是单向的了?而且父组件可以直接通过 this.$refs.childComponent 获取到子组件内部的各种变量和方法,这跟单向流动差远了。 | 
|  |      9beizhedenglong      2021-04-26 22:45:23 +08:00 @qiuxuqin 按照你这这个逻辑,react 通过 callback 回调传数据和 emit 事件传数据又有啥本质区别? | 
|  |      10agdhole      2021-04-27 00:07:40 +08:00 搜一下 immutable | 
|  |      11Rocketer      2021-04-27 01:33:39 +08:00 via iPhone 状态管理算是个 hack,初学先别考虑那个。 React 最重要的基本概念之一就是数据单向流动,只能从父流向子。所以几个组件要想共享数据,就得找个共同的祖先来持有那个数据。 至于更新状态的函数,你也当数据来用就行了,毕竟 JavaScript 里一切皆对象,字符串和函数一视同仁。 | 
|  |      13Rocketer      2021-04-27 02:29:11 +08:00 via iPhone @mongodb 状态管理本质上就是这么个东西啊。所以说初学先别考虑状态管理,等彻底理解了 React 再看。 | 
|  |      14seki      2021-04-27 03:28:35 +08:00 如果一个组件 props 在更新,自己的 state 也在更新,两部分想归到同一个流里面需要思考很多情况,强行写出来性能可能也会比较差 把组件分离成专门用于显示的内容的,以及专门用来处理数据的会让逻辑更简单,实现单向数据流也会更少心智负担 再复杂到一定阶段就会用 context 和 redux / recoil / mobx 之类来把数据处理单独抽出来了 | 
|      15walpurgis      2021-04-27 08:48:02 +08:00 via Android https://zh-hans.reactjs.org/docs/lifting-state-up.html 数据和逻辑提到父组件是官方推荐做法 | 
|  |      16gouflv      2021-04-27 09:06:20 +08:00 via iPhone 1. 如果子组件本身足够复杂,外部可以通过注入类似服务的对象,由服务来间接控制子组件,参考 antd 的 useForm 2. 简单的子组件,用 useImperativeHandle 暴露 api | 
|      17jguo      2021-04-27 09:08:05 +08:00 父组件调用子组件的方法意味着强耦合,不符合 react 的思路 | 
|  |      18forsigner      2021-04-27 10:05:38 +08:00 可以开始了解 React 之状态管理大乱斗了 | 
|      19shunia      2021-04-27 10:20:40 +08:00  1 这和状态管理有啥关系。。。你只要把 <Component /> 理解成 function Component() {} 就好了。 | 
|  |      20AV1      2021-04-27 10:39:58 +08:00 父组件没必要去访问子组件内部的状态。 就像你调用函数的时候,函数外部没必要去获取函数内部的局部变量。 | 
|      21ccraohng      2021-04-27 10:45:15 +08:00  1 只关心组件自己的状态维护,如果你只是改变子组件的数据,可以改成受控组件。比较复杂的函数操作,比如 `slider.next()`,通过 ref  暴露就行。 | 
|  |      22zhaol      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}}/> } } | 
|  |      24towry      2021-04-27 14:52:02 +08:00 看看 react 的 ref 是怎么实现的。 | 
|      25DreamTrace OP @zhaol 没有看懂......`onReady`似乎是`props`上的一个变量,我应该看哪里? | 
|  |      26zhaol      2021-04-28 10:13:32 +08:00 @DreamTrace 就是 ref 的手动实现,子组件通过 onready 把组件实例抛到父组件用一个变量(subComponentName)保存,这样你就可以通过这个变量调用子组件里面的属性和方法了 | 
|      27qiuxuqin      2021-04-28 11:24:48 +08:00 @beizhedenglong react 的数据变更函数是通过父组件传到子组件的,在子组件调用 setState 函数实际上是调用父组件的方法,更改父组件的数据,然后传给子组件的那个数据也跟着变了,所以 React 实际上还是数据单向传送的。vue 的 emit 是在子组件通过事件通知父组件,它这个数据变更是通过子组件往上传的,所以是双向流动。 | 
|      28qiuxuqin      2021-04-28 12:04:11 +08:00 @beizhedenglong 可能我这个解释也不一定准确。我再解释一下:vue 的 v-model 语法其实就算是双向绑定,虽然说它是$props.value 和 emit('change', value )的语法糖。另外如果某个 prop 是个对象,是可以直接修改这个对象的 key 的,这样父组件和子组件也可以的这个对象值也会改变,视图也会更新(当然如果是原始值会报错)。 | 
|  |      29beizhedenglong      2021-04-28 12:24:23 +08:00 via iPhone @qiuxuqin VUE 实现导致的,它不建议你直接改,react 你也可以直接改 | 
|      30qiuxuqin      2021-04-28 19:10:33 +08:00 @beizhedenglong vue 修改 prop 对象内部的值,它还是响应式的,视图会更新。react 直接修改 prop,视图并不会更新,要用 setState 才会触发视图更新。 |