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