从0开始学react,useState,useEffect两个hooks总结,useContext和useRef实现多层组件数据交互

728 阅读6分钟

一、前言

大家好,我是wang,最近换了新工作,公司的技术栈也由vue换做react,开始学习react,既有些兴奋,也有些焦虑。要从0开始学react,怕不能很快适应,但该来的总要来,既然选择了,就要坚持下去。

二、背景

自己看了3天react的资料之后,公司就开始由简单到复杂的让我开始做一些react的需求了,一周下来想把自己对react的理解整理一下,让以后从0开始学习react的同学们有些参考,同时也培养自己总结知识的习惯。

项目用到的技术栈:react:17;ant-design 4.17;TS:4;react-router:5.2。

三、相关知识

1. hooks

以前也接触也react,当时hooks还未发布,还是用class的组件为主,并且自己在vue+ts项目中,也是用“vue-property-decorator”的插件,来模仿class组件的写法,切换到hooks之后,要全面的拥抱函数式编程。用了几天hooks之后,总结以下几点:

为什么要提出hooks这个新功能?

既然已经有了class组件,为什么还要再搞出个hooks呢,很重要的原因有两点:

  • class组件并不适合react组件式的开发
  • 为了简化了逻辑复用
  1. 为什么class的方式并不适合react组件式开发?

    • react的组件一般不会继承另一个组件,所以说class的继承的功能,根本没用到
    • react组件的UI是由状态驱动的,在一个组件内一般不会去调用另一个对象实例的方法,通常都是方法在内部调用或者通过生命周期自执行,或者通过事件执行。所以class的实例化之类功能也几乎没有用到 所以 class的方式并不适合react的组件式开发。
  2. 之前不已经有高阶组件来逻辑复用了吗?为什么还要在函数式组件中推出hooks?

  • 在class组件中,如果想要复用一些逻辑,就要使用复杂的高阶组件,但是高阶组件会产生冗余的组件节点,让调试变得困难,函数式组件中,就要简单的多。 官方提供的10个hooks中,最重要的就属useState()和useEffect()
  1. useState
    • 可以让函数式组件有自己的状态,再用状态去驱动函数重新执行,重新渲染UI。
    • 当然也有缺点,就是如果函数式组件有自己的状态,有多个地方用到这个组件,组件重新执行时,就要恢复状态的过程,这让组件变的更复杂。比如说从外部获取数据,如果有好多地方都用到这个组件,那么用到这个组件的地方都要进行一次数据获取。 解决方案是通过redux的全局状态管理,来管理这些状态。
  2. useEffect(callback,deps),执行副作用。
    • 可以模拟生命周期的一些功能
    • 副作用:一段和当前执行结果无关的代码,也就是说在函数式组件在执行过程中,useEffect的执行是不影响渲染结果的。
    • useEffect()是每次组件render之后,判断依赖并执行。 总结useEffect()使用场景
  3. 每次render后执行,useEffect(callback)
  4. 仅首次加载时执行,useEffect(callback,[])
  5. 依赖项发生变化时才执行,useEffect(callback,[deps])
  6. 组件unmount时才执行,useEffect(()=> unmount(),[])

这里把useEffect比做生命周期并不准确,其实是函数式组件渲染更新的结果,这里修改一下。 关于useEffect的知识可以看我的另一篇关于useEffect理解,那里讲的更为详细和准确。 传送门: 从0开始学react,《useEffect 完整指南》读后感

依赖项:hooks提供了一个监听数据变化的能力,这个数据的变化,可能会引起组件的刷新,也可能执行一个副作用。

  • 依赖项必须是回调函数中用到的,否则声明依赖项是没有意义的。
  • 依赖项应该是一个数组,因为在写回调的时候,你已经可以确定依赖项是什么了。
  • react使用浅比较来判断对象或者数组是否发生了变化,如果你在组件中定义了一个数组,并把它当作依赖项,那么组件每次执行,都会当做是一个新数组,都会执行回调函数。

2. 父子组件通信

  1. 父组件向子组件传递数据:props
  2. 子组件向父组件传递数据:用父组件传的回调函数

看了hooks中的useRef和useContext,想尝试着用这两种方式来实现多层组件的传值

  • 顶层组件如果要通过按钮直接获取孙子级组件的数据,可以使用useRef和useImperativeHandle 相互配合来实现。
  • 父组件如果想传值给孙子级组件,可以使用useContext()来实现。

比如现在有一主页面,分为header、main和right三个组件,right下面有一个form表单,自己单独是一个组件,在header中有一个保存按钮,点保存时,要拿到form中的数据,因为header中的保存,也是调用父组件的保存,所以要在主页面中拿到form表单的数据,这样就是三层的组件嵌套,用普通的方式非常麻烦,可以使用useRef和useImperativeHandle相互配合来开发。

/** 父组件中 **/
// 定义一个右侧组件的引用
const right = useRef(null);
const save = () => {
    const formData = right.current.getFormData();
    ...
}
...其他功能代码
render(){
<Right ref={right} ></Right>
}

/** right组件 **/
// 定义表单组件的引用
function Right(props, ref){
    const myform = useRef(null)
    // 通过 useImperativeHandle,向父组件暴露一个函数,这个函数在去获取表单组件的数据,通常要与forwardRef结合使用
    useImperativeHandle(ref, () => ({
        getFormData: () => {
          return myform.current.getData();
        }
      }));
    ...
    render(){
     <myform ref={myform} />
    }
}
export default Right = forwardRef(Right);


/** myform组件 **/
// 把获取表单数据的函数暴露出去
function myForm(props, ref){
    useImperativeHandle(ref, () => ({
        getData: () => {
          return form.getFieldsValue();
        }
      }));
}
  
  // 还需要用forward把组件导出去
export const myForm = forwardRef(myForm);

有时候我们需要把父组件的数据传递到孙子级组件,可以用useContext来实现

/** 父组件 **/
// 需要把这个context导出去,在孙子级组件中引入 
export const myContext = createContext('') 

render(){
// 把right组件用 <myContext.Provider> 包起来,value属性就是要向下传递的值
    <myContext.Provider value={myValue}>
    <right></right>
    </myContext.Provider>
}

/** myform组件 **/
import myContext from 'myContext'
const newValue = useContext(myContext);
// 在孙子组件中可以使用newValue

以上仅个人学习整理,如有错误之处,还望大家指正。