一、前言
大家好,我是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组件式的开发
- 为了简化了逻辑复用
-
为什么class的方式并不适合react组件式开发?
- react的组件一般不会继承另一个组件,所以说class的继承的功能,根本没用到
- react组件的UI是由状态驱动的,在一个组件内一般不会去调用另一个对象实例的方法,通常都是方法在内部调用或者通过生命周期自执行,或者通过事件执行。所以class的实例化之类功能也几乎没有用到 所以 class的方式并不适合react的组件式开发。
-
之前不已经有高阶组件来逻辑复用了吗?为什么还要在函数式组件中推出hooks?
- 在class组件中,如果想要复用一些逻辑,就要使用复杂的高阶组件,但是高阶组件会产生冗余的组件节点,让调试变得困难,函数式组件中,就要简单的多。 官方提供的10个hooks中,最重要的就属useState()和useEffect()
- useState
- 可以让函数式组件有自己的状态,再用状态去驱动函数重新执行,重新渲染UI。
- 当然也有缺点,就是如果函数式组件有自己的状态,有多个地方用到这个组件,组件重新执行时,就要恢复状态的过程,这让组件变的更复杂。比如说从外部获取数据,如果有好多地方都用到这个组件,那么用到这个组件的地方都要进行一次数据获取。 解决方案是通过redux的全局状态管理,来管理这些状态。
- useEffect(callback,deps),执行副作用。
可以模拟生命周期的一些功能。- 副作用:一段和当前执行结果无关的代码,也就是说在函数式组件在执行过程中,useEffect的执行是不影响渲染结果的。
- useEffect()是每次组件render之后,判断依赖并执行。 总结useEffect()使用场景
- 每次render后执行,useEffect(callback)
- 仅首次加载时执行,useEffect(callback,[])
- 依赖项发生变化时才执行,useEffect(callback,[deps])
组件unmount时才执行,useEffect(()=> unmount(),[])
这里把useEffect比做生命周期并不准确,其实是函数式组件渲染更新的结果,这里修改一下。 关于useEffect的知识可以看我的另一篇关于useEffect理解,那里讲的更为详细和准确。 传送门: 从0开始学react,《useEffect 完整指南》读后感
依赖项:hooks提供了一个监听数据变化的能力,这个数据的变化,可能会引起组件的刷新,也可能执行一个副作用。
- 依赖项必须是回调函数中用到的,否则声明依赖项是没有意义的。
- 依赖项应该是一个数组,因为在写回调的时候,你已经可以确定依赖项是什么了。
- react使用浅比较来判断对象或者数组是否发生了变化,如果你在组件中定义了一个数组,并把它当作依赖项,那么组件每次执行,都会当做是一个新数组,都会执行回调函数。
2. 父子组件通信
- 父组件向子组件传递数据:props
- 子组件向父组件传递数据:用父组件传的回调函数
看了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
以上仅个人学习整理,如有错误之处,还望大家指正。