React技术栈
2020-09-14 14:33:30 5 举报
AI智能生成
React技术栈梳理
作者其他创作
大纲/内容
React技术栈
React简介
React是什么
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。使用 React 可以将一些简短、独立的代码片段组合成复杂的 UI 界面,这些代码片段被称作“组件”
特点
声明式
React 可以非常轻松地创建用户交互界面。为你应用的每一个状态设计简洁的视图,在数据改变时 React 也可以高效地更新渲染界面
传统web开发都是手动操作dom,有了声明式,我们可以跳过对dom层的直接操作,只需去维护和操作数据即可
组件化
什么是组件化
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素
优点
增强代码重⽤性,提⾼开发效率
简化调试步骤,提升整个项⽬的可维护性
便于协同开发
降低耦合性
React Component(React组件)
函数组件
class组件
高阶组件(HOC)
定义
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式
具体而言,高阶组件是参数为组件,返回值为新组件的函数
使用
const EnhancedComponent = higherOrderComponent(WrappedComponent);
注意事项
不要在 render ⽅法中使⽤ HOC
HOC 不会修改传入的组件,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用
组件间通信,即数据传递
props
数据是通过 props 属性⾃上⽽下(由⽗及⼦)进⾏传递的
单向数据流
props从组件外部向内部传递的数据;组件内部只读不修改
Context
Context 提供了⼀种在组件之间共享此类值的⽅式,⽽不必显式地通过组件树的逐层传递props
React.createContext
创建⼀个 Context 对象。当 React 渲染⼀个订阅了这个 Context 对象的组件,这个组件会从组件树中离⾃身最近的那个匹配的 Provider 中读取到当前的 context 值
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。这有助于在不使用 Provider 包装组件的情况下对组件进行测试
const MyContext = React.createContext(defaultValue);
Context.Provider
Provider 接收⼀个 value 属性,传递给消费组件,允许消费组件订阅 context 的变化。⼀个 Provider可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使⽤,⾥层的会覆盖外层的数据
当 Provider 的 value 值发⽣变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部consumer 组件都不受制于 shouldComponentUpdate 函数,因此当consumer 组件在其祖先组件退出更新的情况下也能更新
<MyContext.Provider value={/* 某个值 */}>
Context.Consumer
React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。这个函数接收当前的 context 值,返回⼀个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider, value 参数等同于传递给 createContext() 的 defaultValue
可嵌套多个Consumer
Class.contextType
挂载在class上的contextType属性会被重赋值为⼀个由React.createContext() 创建的Context对象。这能让你使⽤ this.context 来消费最近 Context 上的那个值。你可以在任何⽣命周期中访问到它,包括 render 函数中
只能通过该API订阅单一的context
useContext
接收⼀个 context 对象( React.createContext 的返回值)并返回该 context 的当前值。当前的context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。只能⽤在function组件中
使用Context
创建Context => 获取Provider和Consumer => Provider提供值 => Consumer消费值
因为 context 会使⽤参考标识(reference identity)来决定何时进⾏渲染,这⾥可能会有⼀些陷阱,当provider 的⽗组件进⾏重渲染时,可能会在 consumers 组件中触发意外的渲染。
为了防⽌这种情况,将 value 状态提升到⽗节点的 state ⾥
总结
在React的官⽅⽂档中, Context 被归类为⾼级部分(Advanced),属于React的⾼级API,建议不要滥⽤
React-Router(路由)
简介
子主题
Redux(React状态管理)
Redux 是 JavaScript 状态容器,提供可预测化的状态管理
动机
随着 JavaScript 单页应用开发日趋复杂,JavaScript 需要管理比任何时候都要多的 state (状态)。 这些 state 可能包括服务器响应、缓存数据、本地生成尚未持久化到服务器的数据,也包括 UI 状态,如激活的路由,被选中的标签,是否显示加载动效或者分页器等等
管理不断变化的 state 非常困难,state 在什么时候,由于什么原因,如何变化已然不受控制
我们总是将两个难以理清的概念混淆在一起:变化和异步
核心概念
使用普通对象来描述应用的state
要想更新 state 中的数据,你需要发起一个 action
reducer是一个无任务副作用的纯函数,reducer 只是一个接收 state 和 action,并返回新的 state 的函数
三大原则
单一数据源
整个应用的state被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中
state是只读的
唯一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers
Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。刚开始你可以只有一个 reducer,随着应用变大,你可以把它拆成多个小的 reducers,分别独立地操作 state tree 的不同部分
生态系统
不同框架绑定
react-redux — React
ng-redux — Angular
。。。
中间件
redux-thunk — 用最简单的方式搭建异步 action 构造器
redux-promise — 遵从 FSA 标准的 promise 中间件
redux-axios-middleware — 使用 axios HTTP 客户端获取数据的 Redux 中间件
redux-observable — Redux 的 RxJS 中间件
redux-logger — 记录所有 Redux action 和下一次 state 的日志
redux-saga — Redux 应用的另一种副作用 model
路由
redux-simple-router — 保持 React Router 和 Redux 同步
redux-router — 由 React Router 绑定到 Redux 的库
组件
redux-form — 在 Redux 中时时持有 React 表格的 state
react-redux-form — 在 React 中使用 Redux 生成表格
增强器(Enhancer)
基础
Action
Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源。一般来说你会通过 store.dispatch() 将 action 传到 store
Action 本质上是 JavaScript 普通对象。我们约定,action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作
Action 创建函数,返回一个action对象
Reducer
reducer 就是一个纯函数,接收旧的 state 和 action,返回新的 state
注意
不要在reducer修改传入参数
不要在reducer执行有副作用的操作,如 API 请求和路由跳转
不要在reducer调用非纯函数,如 Date.now() 或 Math.random()
拆分 Reducer
user(用户模块)
cart(购物车模块)
product(商品模块)
Store
Store 就是把action,reducer联系到一起的对象
职责
维持应用的 state
提供 getState() 方法获取 state
提供 dispatch(action) 方法更新 state
通过 subscribe(listener) 注册监听器
通过 subscribe(listener) 返回的函数注销监听器
数据流
严格的单向数据流是 Redux 架构的设计核心
Redux 应用中数据的生命周期
调用 store.dispatch(action)
Redux store 调用传入的 reducer 函数
根 reducer 应该把多个子 reducer 输出合并成一个单一的 state 树
Redux store 保存了根 reducer 返回的完整 state 树
React-Redux(搭配React)
安装 React Redux
npm install --save react-redux
容器组件
描述如何展现(骨架、样式)
不直接使用 Redux
数据来源:props
数据修改:从 props 调用回调函数
调用方式:手动调用
展示组件
描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源:监听 Redux state
数据修改:向 Redux 派发 actions
调用方式:通常由 React Redux 生成
connect()
调用reacr-redux中的connect()方法,把展示组件和 Redux 连接起来
传入Store
在根组件通过react-redux的Provider组件统一注入Store
Redux 和 React 之间没有关系。Redux 支持 React、Angular、Ember、jQuery 甚至纯 JavaScript
大部分的组件都应该是展示型的,但一般需要少数的几个容器组件把它们和 Redux store 连接起来
高级
异步Action
异步数据流
默认情况
createStore() 所创建的 Redux store 没有使用 middleware,所以只支持 同步数据流
applyMiddleware()
通过applyMiddleware()增强createStore(),方便描述异步action
Middleware(中间件)
redux-logger
redux-thunk
React生命周期
组件的创建,运行,销毁的过程中,总是伴随着各种各样的事件;这些在组件特定时期,触发的事件,叫做组件的生命周期,也叫做生命钩子函数
生命周期状态
Mount(组件挂载阶段)
constructor()
React数据的初始化,它接受两个参数:props和context,当想在函数内部使用这两个参数时,需使用super()传入这两个参数
componentWillMount()
这个钩子用的相对较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时
render()
render函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过其diff算法比较更新前后的新旧DOM树,比较以后,找到最小的有差异的DOM节点,并重新渲染
render()是纯函数,不能在这个钩子函数中调用this.setState(),会有改变组件状态的副作用
return 一个React元素
componentDidMount()
组件第一次渲染完成,此时dom节点已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染
Update(组件更新阶段)
componentWillReceiveProps (nextProps)
在接受父组件改变后的props需要重新渲染组件时用到的比较多
接受一个参数nextProps
通过对比nextProps和this.props,将nextProps的state为当前组件的state,从而重新渲染组件
主要用于性能优化(部分更新)
唯一用于控制组件重新渲染的生命周期,由于在react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以阻止组件的更新
因为react父组件的重新渲染会导致其所有子组件的重新渲染,这个时候其实我们是不需要所有子组件都跟着重新渲染的,因此需要在子组件的该生命周期中做判断
render()是纯函数,不能在这个钩子函数中调用this.setState(),会有改变组件状态的副作用<br>
Unmount(组件销毁或组件卸载阶段)
componentWillUnmount()
在此处完成组件的卸载和数据的销毁
移除所有组建中的监听 removeEventListener
React-Hooks
Hook可以让你在不编写class的情况下使用state以及其他的React特性
在组件之间复用状态逻辑很难
你可以使用 Hook 从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook 使你在无需修改组件结构的情况下复用状态逻辑
复杂组件变得难以理解
Hook 将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据),而并非强制按照生命周期划分。你还可以使用 reducer 来管理组件的内部状态,使其更加可预测
难以理解的 class
Hook完全拥抱函数,你可以不写class来使用React的所有特性
内置hook
useState
返回一个 state,以及更新 state 的函数
一个React函数组件中可以多次调用useState
React 会确保 setState 函数的标识是稳定的,并且不会在组件重新渲染时发生变化
与 class 组件中的 setState 方法不同,useState 不会自动合并更新对象。你可以用函数式的 setState 结合展开运算符来达到合并更新对象的效果
useReducer 是另一种可选方案,它更适合用于管理包含多个子值的 state 对象
useEffect
useEffect(didUpdate)
该 Hook 接收一个包含命令式、且可能有副作用代码的函数
在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性
使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行
默认情况下,effect 将在每轮渲染结束后执行,但你可以选择让它 在只有某些值改变的时候才执行
清除effect
通常,组件卸载时需要清除 effect 创建的诸如订阅或计时器 ID 等资源。要实现这一点,useEffect 函数需返回一个清除函数
为防止内存泄漏,清除函数会在组件卸载前执行。另外,如果组件多次渲染(通常如此),则在执行下一个 effect 之前,上一个 effect 就已被清除
effect 的执行时机
与 componentDidMount、componentDidUpdate 不同的是,在浏览器完成布局与绘制之后,传给 useEffect 的函数会延迟调用。这使得它适用于许多常见的副作用场景,比如设置订阅和事件处理等情况,因此不应在函数中执行阻塞浏览器更新屏幕的操作
然而,并非所有 effect 都可以被延迟执行。例如,在浏览器执行下一次绘制前,用户可见的 DOM 变更就必须同步执行,这样用户才不会感觉到视觉上的不一致。(概念上类似于被动监听事件和主动监听事件的区别。)React 为此提供了一个额外的 useLayoutEffect Hook 来处理这类 effect。它和 useEffect 的结构相同,区别只是调用时机不同
虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。React 将在组件更新前刷新上一轮渲染的 effect
effect 的条件执行
默认情况下,effect 会在每轮组件渲染完成后执行。这样的话,一旦 effect 的依赖发生变化,它就会被重新创建
可以给 useEffect 传递第二个参数,它是 effect 所依赖的值数组
如果想执行只运行一次的 effect(仅在组件挂载和卸载时执行),可以传递一个空数组([])作为第二个参数。这就告诉 React 你的 effect 不依赖于 props 或 state 中的任何值,所以它永远都不需要重复执行
如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直持有其初始值。尽管传入 [] 作为第二个参数有点类似于 componentDidMount 和 componentWillUnmount 的思维模式,但我们有 更好的 方式 来避免过于频繁的重复调用 effect。除此之外,请记得 React 会等待浏览器完成画面渲染之后才会延迟调用 useEffect,因此会使得处理额外操作很方便
const value = useContext(MyContext);
接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定
别忘记 useContext 的参数必须是 context 对象本身
调用了 useContext 的组件总会在 context 值变化时重新渲染
useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer>
useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context
useReducer
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递 dispatch 而不是回调函数
指定初始 state
有两种不同初始化 useReducer state 的方式,你可以根据使用场景选择其中的一种。将初始 state 作为第二个参数传入 useReducer 是最简单的方法
惰性初始化
你可以选择惰性地创建初始 state。为此,需要将 init 函数作为 useReducer 的第三个参数传入,这样初始 state 将被设置为 init(initialArg)
React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变
React 不使用 state = initialState 这一由 Redux 推广开来的参数约定。有时候初始值依赖于 props,因此需要在调用 Hook 时指定
useCallback
返回一个 memoized 回调函数
把内联回调函数及依赖项数组作为参数传入 useCallback,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新
依赖项数组不会作为参数传给回调函数。虽然从概念上来说它表现为:所有回调函数中引用的值都应该出现在依赖项数组中
useMemo
返回一个memoized值
把“创建”函数和依赖项数组作为参数传入 useMemo,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算
记住,传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo
如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值
你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证
依赖项数组不会作为参数传给“创建”函数。虽然从概念上来说它表现为:所有“创建”函数中引用的值都应该出现在依赖项数组中
useRef
const refContainer = useRef(initialValue);
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变
useLayoutEffect
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新
如果你正在将代码从 class 组件迁移到使用 Hook 的函数组件,则需要注意 useLayoutEffect 与 componentDidMount、componentDidUpdate 的调用阶段是一样的。但是,我们推荐你一开始先用 useEffect,只有当它出问题的时候再尝试使用 useLayoutEffect
hook规则
只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确
尽可能使用标准的 useEffect 以避免阻塞视觉更新
只在 React 函数中调用 Hook
在 React 的函数组件中调用 Hook
在自定义 Hook 中调用其他 Hook
自定义hook
通过自定义 Hook,可以将组件逻辑提取到可重用的函数中
约定
自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook
与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么,但是它的名字应该始终以 use 开头
使用自定义hook
请务必在函数组件中使用自定义hook
在两个组件中使用相同的 Hook不会共享state
自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的
每次调用 Hook,它都会获取独立的 state
React Virtual DOM
Virtual DOM是一种编程概念,在这个概念里,UI以一种理想化的,或者说是虚拟的表现形式被保存在内存中,并通过ReactDom等类库使之与真实DOM同步,这一过程叫做协调
why
DOM操作很慢,轻微的操作都可能导致页面重新排版,非常耗性能。相对于DOM对象,js对象处理起来更快,而且更简单。通过diff算法对比新旧vdom之间的差异,可以批量的、最小化的执行dom操作,从而提高性能
where
React中用JSX语法描述视图,通过babel-loader转译后它们变为React.createElement(...)形式,该函数将生成vdom来描述真实dom。将来如果状态变化,vdom将作出相应变化,再通过diff算法对比新老vdom区别从而做出最终dom操作
how
JSX
什么是JSX
React 使用 JSX 来替代常规的 JavaScript。
为什么需要JSX
开发效率:使用 JSX 编写模板简单快速
执行效率:JSX编译为 JavaScript 代码后进行了优化,执行更快
类型安全:在编译过程中就能发现错误
原理
babel-loader会预编译JSX为React.createElement(...)
与vue的异同
react中虚拟dom+jsx的设计一开始就有,vue则是演进过程中才出现的
jsx本来就是js扩展,转义过程简单直接的多;vue把template编译为render函数的过程需要复杂的编译器转换字符串-ast-js函数字符串
核心api
React.createElement:创建虚拟DOM
React.Component:实现自定义组件
ReactDOM.render:渲染真实DOM
0 条评论
下一页
为你推荐
查看更多