React回顾笔记(1)

182

前序

由于之前工作中都是直接上手进行React的开发,虽然做了很多React以及ReactNative的工作,但是仅仅是在实操中进行了掌握,并没有细致的学习研究文档,以及底层原理。虽然开发不成问题,但本着一颗钻研的心(其实就是为了面试 ^_^),还是决定对React进行一次深入的回顾与学习,因此决定在接下来时间把官方文档彻底过一遍,以此在实践的基础上,从原理与表述上得到一定的提升,废话不多说,开始。

基础篇

1. JSX

  • 它是React中的一个标签语法,既不是字符串,也不是HTML,它是JavaScript的一个语法扩展(具备Javascript的全部功能),在React中我们用它来描述我们想要呈现出来的内容。

  • 官网中还有一段对JSX的描述是:实际上,JSX 仅仅只是React.createElement(component, props, ...children) 函数的语法糖。也就是说,我们在页面上所书写的JSX的表达式,最终都会被转化为React.createElement(component, props, ...children)的形式,这一点我们可以使用官方提供的在线的 Babel 编译器尝试一下,这一点也证明了为什么我们的页面明明没有使用React,为什么我们在开头还必须要引入React。

  • 在JSX中规定,用户自定义的组件必须以大写开头,以小写字母开头的默认为HTML标签

return <MyComponent /> // 用户自定义组件
return <div>123</div> // html标签
  • 在JSX中传递props我们可以采用 { } 包裹,进行传递
// 变量我们使用 {} 包裹进行传递,字符串直接使用引号即可
<MyComponent name={username} sex="female" /> 
// 在MyComponent组件中,我们可以这样使用name和sex属性,React会帮我们把props进行合并,我们在组件中,直接使用ES6解构即可
const MyComponent: FC<IProps> = (props) => {
 const { name,sex } = props
}
  • JSX的子元素可以是字符串,可以是html标签,可以是用户自定义组件,也可以表达式,函数等等,因为jsx本身就是JavaScript的一个扩展,所以你可以在它里面使用JavaScript的语法,注意表达式、函数、变量等要写在 { } 内,这使得React变得无比灵活,需要注意的是:布尔类型Null 以及 Undefined 将会忽略,不会渲染。我们也可以利用这个,结合条件渲染,来完成一些需求。
// 这里我们描述了一个div标签,然后展示了一个hello world的字符串
const jsx = <div>hello world!</div>
// 它也可以写表达式或者变量,只要你在想书写表达式或者变量的地方,用 {} 将他们包裹起来,jsx会认为这是动态的,而不会把它当成字符串解析
const jsx1 = <div>{1+1}</div>
// 函数、使用ES6模板字符串
return <div onClick={ () => { } }>点击事件`${name}`</div>

2. 组件(组件化开发)

  • 组件可以理解为是描述特定内容的一段代码片段,它在React中最明显表现形式是以函数或类存在,所以在React中我们也可以说组件就是一个函数(函数式组件),或者一个类(类式组件),它接收一个props,返回用于描述特地内容的React元素。
  • 现版本的React,更加推荐使用函数式组件,他的性能要更好,并且天然对类型友好,十分有利于结合Ts来进行开发,有了hook以后,我们也可以在函数式组件中定义状态。
// 这就是一个最简单的组件,它返回一个包裹hello的div,这里所指的div并不是严格意义上的html的div,更确切地说他是一个react元素
// 它还接收一个props,并且传递了一个text数据
const demo = (props) => {
const [state,setState] = useState() // hook定义组件状态
 return <div>hello,{props.text}</div>
}
  • 扩展:组件化与模块化
    • 组件化:就是将一个复杂的功能按照一定的规则拆分成一个个小的片段,这些小的片段负责自己独立的部分,最终将这些小片段拼成完整的一个功能模块,这些小片段我们可以称为组件,这种开发方式就是组件化开发。例如:一个登录功能,我们可以将输入部分作为一个组件,提交部分作为一个组件,一个负责UI展示,一个负责登录逻辑。这样大大降低代码耦合,提高了代码复用,也易于后期的维护,但请记住,不要为了组件化而去组件化,我们应该有自己的组织规则,不然,反而让代码过于分散,反而降低了效率
    • 模块化:组件化是将功能拆分,而模块化则是在整个业务逻辑的基础上,进行功能的划分。即业务框架,例如:登录,注册,首页。他将相同功能的代码组织到一起,模块化针对不同的业务有自己的组织,而组件化,尽可能是能达到在不同业务中复用。不过,在React中,万物皆组件,我们根据公司规范和自己的代码习惯来进行组织即可。

3. Props

  • props是组件的参数,我们可以通过props拿到父组件传来的数据,父子(父传子)组件的通信,我们就是利用它
  • props具有只读性,也就是我们只可以使用它的数据,绝不可以更改它。官方给出了这样一句话:React 非常灵活,但它也有一个严格的规则,所有react组件都必须像纯函数一样保护它们的 props 不被更改。
  • 所谓的纯函数,就是无论何时何地,给定的输入,都会有相同的输出,并且不会产生副作用
  • 在Props中有一个比较特殊的值,就是children,它很像Vue中的插槽(slot),但它实际就是一个对象,我们用它来渲染子组件,它代表了任意的React元素,他最终会被真正的React元素替换填充。
// 父组件
const Father = ({children}) => {
return <div>
  <p>我是父组件</p>
  {children}
</div>
}
// 子组件
const Son = () => {
return <Father>
<span>我是children</span>
</Father>
}
// 最终生成的结构就是下面这个样子
<div>
  <p>我是父组件</p>
  <span>我是children</span>
</div>

4. State

  • State我理解得它就是组件的状态,更简单点就是当前组件的数据仓库,它管理了当前组件自己的所有数据(状态)
  • State无法直接修改,在类式组件需要调用setState(),在函数式组件中,我们通过hook的方式更新状态
  • State的更新可能是异步的,这也是很多时候会碰到的坑,设置了值却仍然读取不到,很有可能是因为它的异步
  • State的更新会被合并,也就是多次setState可能会被合并成一次,这是React底层的优化
  • State是单向数据流,也就是当前State只能影响当前组件,它也可以通过props向下传递,影响下级组件

  • 官方举例:如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。

5. 生命周期

  • 生命周期函数就是在某一个特定的时间点,会自动执行的函数
  • 生命周期在类式组件中更为明显,讲解的话需要使用类式组件,因为现在已经提倡函数式组件,而在函数式组件中,没有了原先的生命周期函数,但我们可以使用useEffect来模拟
  • 上面这幅图是React的一个生命周期图例,网上讲解它的资料也数不胜数,可以自行百度查看,现在在函数式组件中,我们利用useEffect这个hook几乎都可以模仿
// 这个hook如果第二个参数是空数组,不传依赖状态,会在 Dom 加载完成后只执行一次,也就是componentDidMount()
// 而它的第二个参数是一个依赖数组,这个hook会依赖第二个参数数组中的数据的变化而进行执行
// 当数组中数据变动时,这个hook就会被执行一次。也就对应了状态改变之后的那些生命周期函数
// 第二个参数如果不传,还在内部改变了状态的话,会造成死循环,这点要注意
useEffect(()=>{
    if(state){ // shouldComponentUpdate
     // true执行,否则不执行,相当状态改变,更新阶段
    }
    return () => {
    // 返回一个函数,相当于卸载阶段
    }
},[state])

6. 事件处理与条件渲染

6.1 事件处理

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式,而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
import React, { FC } from 'react'

const Demo: FC = () => {
const handleClick = () => {
// 点击处理逻辑
}
// 事件绑定
 return <div onClick={handleClick}>点击</div>
}
  • 至于React底层是怎么实现的这个事件系统,有兴趣的可以去看看这个 https://react.jokcy.me/book/features/event-injection.html

6.2 条件渲染

  • 条件渲染:在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
  • 我们可以使用if或者条件运算符(短路,三目等),来控制我们的UI渲染,使用null阻止渲染
// 根据用户登录状态,渲染不同UI
function UserGreeting(props) {
  return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
  return <h1>Please sign up.</h1>;
}

// if
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  if (isLoggedIn) {
    return <UserGreeting />;
  }
  return <GuestGreeting />;
}
// 三目
function Greeting(props) {
  const isLoggedIn = props.isLoggedIn;
  return isLoggedIn ? <UserGreeting /> : <GuestGreeting />
}

7. React中的Key

  • key不会显式的暴露到props中,react隐藏了它,key是react在diff或者优化时用到的,为了性能,我们尽量给组件都写上key,尤其是列表,不写控制台会给出醒目提醒
  • 对于会变动的列表,我们的key一定不要用index,因为列表发生变动,原本都是10个,之后中间去掉一个,那么这个列表的索引就会发生改变,失去了唯一性的key,这样React在进行diff比对的时候,就不能一下找到它们了,甚至还会引发其他问题。
  • 这里讲一个由key引发的问题,在一次项目中,当时框架使用的是蚂蚁金服的umi,当时那个业务需要100多个菜单,我们用的是约定式路由,也就是需要建100多个文件夹,来生成路由,实现点击不同菜单的切换,而umi提供了一个非常好用的动态路由,利用它,我们删除掉了100多个文件夹,只使用了三个文件夹就搞定了[第一级文件夹]/[第二级文件夹]/[第三级文件夹]/index.tsx,我们不关心它路由什么样子,后台返回是什么就是什么,完成由他自己动态生成,这样一下精简了项目。但是由于路由全部是动态生成的,我们只用到了这同一个文件下的同一个index,所有菜单下引用的都是index.tsx这个组件,由于React的优化机制,以及我没加key,就导致,这个组件只会挂载一次,之后就被缓存,再也不更新了,即使你点击不同菜单,它显示的仍然是最初那个组件解决方案:因为不同菜单生成的路由必然不同,所以把路由当作组件的key,完美解决<Fragment key={history.location.pathname}></Fragment>

8. 状态提升

  • 有时候,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
  • 这个问题其实就是组件间的数据共享,或者说数据传递,因为React是单向数据流,如果我们不使用Redux等库,要在兄弟之间共享数据,那么最简单的就是将共享的数据提到他们的共同父组件中,然后通过props向下传递,这就是状态提升