type
status
date
slug
summary
tags
category
icon
password
概念
index.js:
- 项目入口
- react和react-dom/client:项目必要的两个核心包
- 导入项目根组件,将根组件渲染到id为root的节点上,也就是将项目的根组件渲染到public的index.html的id为root的div节点上
JSX
基础语法
JSX:JavaScript+XML(HTML)
:- 表示在JS代码中编写HTML模板结构,是React中编写UI模板的方式
- HTML声明式的模板写法+JS的可编程能力,JSX 会让你把标签放到 JavaScript 中。而大括号会让你 “回到” JavaScript 中
- 本质:JS的语法扩展,浏览器本身不能识别,需要通过解析工具做解析之后才能在浏览器中运行
- 使用JS表达式:大括号语法,里面仅支持表达式,不支持语句(if、变量声明之类的)
- 组件不能返回多个JSX标签,必须将其包裹到一个共享的父级中,哪怕是用空标签包含:<>内容</>
列表渲染/条件渲染
- 列表渲染:
- 用原生js的map方法
- key值:独一无二的字符串或者number id。是react框架内部使用,用来提升更新性能的
- 提高虚拟dom的更新性能
- 若不设置key,可能触发一些bug:比如说只更新第二行,因为没有key导致其他的元素也更新了,或者最后的结果不是更新,而是做成了多一行其他的
- 为了触发过渡的效果
v-for中key的作用有点像:key属性是dom元素的唯一标识,如果没有这个东西的话,在dom里面添加/删除一个元素就必须整体进行重绘,但如果有了kitemey,重绘之前vue会检查哪些是需要重绘的新元素,如果没有的话就直接用之前的,避免资源浪费。
- 基础条件渲染
- 复杂条件渲染
自定义函数(返回不同JSX模板)+if判断语句
事件绑定
语法:on+事件名称={ 事件处理程序(回调函数) }
获取事件对象:在事件回调函数里面写一个形参
传递自定义参数:箭头函数

组件
一个组件就是首字母大写的函数,内部存放了组件的逻辑和视图UI,渲染组件就是把组件当成标签书写
使用
class
的方式来创建组件:- this的指向:
- 在类组件中,this指向当前组件实例
- this.props可以访问当前组件的props,而这个props是从父组件传进来的
获取组件的引用
函数形式:
- 当组件挂载完成后,
react
会调用ref
属性里面对应的函数
- react会把这个组件的实例作为参数传给这个函数
- 比如说像表单校验的时候,就可以用这个引用来调用这个组件有的函数/方法:
类组件创建:
- 使用
React.createRef()
创建一个应用
- 通过
ref={this.formRef}
将该引用绑定到表单上
使用useRef创建:类组件不能用Hook
useRef()
是 React Hook,只能在函数组件或自定义 Hook 中使用,不能在类组件中使用,类组件用React.createRef()
创建引用而函数组件用useRef()
原因:函数式组件没有this,没办法用this.form = xxx这种形式来为引用赋值,React 提供了
useRef()
这个 Hook,用于在函数组件中“存储跨渲染的引用”:父子组件通信
单向数据流
react采用的是单向数据流,而不是双向数据绑定,即数据只能从父组件流到子组件:
原因:
- 这种设计模式使得应用程序的结构更加清晰、易于理解和调试。相比之下,双向数据绑定会导致数据流动方向不确定,增加了代码的复杂度和难度
- 在双向数据绑定中,当数据发生变化时,系统需要同时更新视图和数据模型,这会导致性能瓶颈。而在单向数据流中,数据的更新只会从父组件向子组件进行,这样可以避免不必要的视图更新,提高了应用程序的性能表现
- 在双向数据绑定中,由于数据的修改可能来自于多个组件,造成了数据的不可预测性。而在单向数据流中,数据的修改只能由父组件或本身进行,这样可以更好地控制应用程序的逻辑,减少了错误发生的概率
- React支持服务端渲染,这对于SEO和性能都非常重要。在双向数据绑定中,由于数据的修改可能来自于客户端,这使得服务端渲染变得更加困难。而在单向数据流中,由于数据的修改只能来自于服务端或本身,这样可以更方便地实现服务端渲染
具体来说,React应用程序中的数据分为两种类型:
- Props:由父组件传递给子组件的只读属性,不能在子组件中修改;
- 由于Props属性是只读的,因此子组件不能直接修改它们的值。如果需要更新Props属性的值,那么必须由父组件进行更新并重新传递给子组件。
- State:组件内部维护的可变状态,可以通过
setState()
方法进行修改。 - State状态是可变的,可以在组件内部通过
setState()
方法进行修改。当状态发生变化时,React会自动重新渲染并将新的状态值传递给子组件,从而更新子组件的界面
总之,react的数据只能由父组件流向子组件,要改变父组件也只能由组件自身发起
组件间共享数据
这段代码中两个按钮的计数器都是独立更新的,现在我想让两个按钮使用同一个计数:

也就是要将
count
变量的状态移动到MyButton
组件的上一级:MyApp
,然后使用大括号的写法将MyApp
中的count
的状态传给每个子组件:使用这种方式传递的信息被称作 prop
修改子组件以获取从父组件传过来的count和onClick:
当你点击按钮时,
onClick
处理程序会启动。每个按钮的 onClick
prop 会被设置为 MyApp
内的 handleClick
函数,所以函数内的代码会被执行。该代码会调用 setCount(count + 1)
,使得 state 变量 count
递增。新的 count
值会被作为 prop 传递给每个按钮,因此它们每次展示的都是最新的值。这被称为“状态提升”。通过向上移动 state,我们实现了在组件间共享它。父传子
使用类的方式来创建组件:
- 子组件:
- 在组件的constructor里面用props接收父组件传来的数据
- 用state来定义组件内部维护的可变状态(将这个组件所有要用的变量都装在这个里面),代码中用
this.setState({ count: this.state.count + 1 })
来修改count的值而other
保持不变: this.setState({ count: this.state.count + 1 })
只更新了count
,other
的值 不会丢失,仍然是'test'
。- React 会对
this.state
进行浅合并(shallow merge),而不是直接替换整个 state。
可以写接口来规定props和state需具备的属性:
- 父组件:
在
Counter
组件中声明了一个Props属性initialCount
,并在构造函数中使用props
参数来初始化状态变量。这样,当App
组件传递initialCount
属性时,Counter
组件就能够正确地使用该属性使用函数的方式创建组件:也是一样的
子传父
用回调函数:
- 父组件:写一个回调函数传给子组件,这个函数可以修改父组件里面的
state
:
- 子组件:调用父组件传来的回调函数,以此来给父组件传值
父组件调用子组件的方法
子组价将方法绑定到ref上,父组件获取子组件的ref来调用里面的方法
- 子组件:
- 使用
React.forwardRef
包装子组件 - 将子组件的方法绑定到
ref
- 父组件:将ref(useRef返回的)传递给子组件
使用Hook
- 以
use
开头的函数被称为 Hook,useState是内置的一个hook,也可以自己编写hook
- React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。 React Hooks 就是那些钩子
- 逻辑复用+响应式:组件复用的话样式也要复用,函数复用的话没有响应式
有什么用?
- 可以在不编写 class 的情况下使用 state 及其他 react 的特性
- 可以将组建逻辑提取到可重用的函数中
useState()
useState是一个React Hook函数,它允许我们向组件添加一个状态变量,从而影响组件的渲染结果
类似于toRef

计数器:
官网例子:实现两个独立的点击+次数的按钮
修改状态的规则:
- 在React中,状态被认为是只读的,我们应该始终替换它而不是修改它,直接修改状态不能引发视图更新
- 相当于用setCount,传入一个新值,这个方法在内部可以实现视图更新
修改对象状态:
- 规则:对于对象类型的状态变量,应该始终传给set方法一个全新的对象来进行修改
在 React 中,直接修改状态对象不是推荐的做法。比如这段写法就不好:
正确做法是创建一个新对象,并用
setForm()
替换掉原来的:这样 React 才能检测到状态变化,触发组件重新渲染。
当你点击按钮时,它会:
- 调用
setForm
更新form
的值;
- 使用展开运算符
...form
复制form
原有的属性;
- 然后用
name: 'lucy'
覆盖form
中的name
字段。
setXXX里面传一个函数比较好:
- 直接设置可能有问题:
useContext()
如果需要在组件之间共享状态,可以使用
useContext()
- 使用
createContext
在组件外部建立一个Context
:
- 将需要共享上下文的组件封装在里面:
- 子组件通过useContext获取上下文中的属性:
useEffect()
useEffect()
接受两个参数。第一个参数是一个函数,异步操作的代码放在里面。第二个参数是一个数组,用于给出 Effect 的依赖项,只要这个数组发生变化,useEffect()
就会执行。第二个参数可以省略,这时每次组件渲染时,就会执行useEffect()
返回值:
useRef()
用于创建一个可变的引用对象,读写都通过
.current
来访问:- 访问dom元素
- 存储可变值并且不会触发重新渲染:这跟vue的ref不一样,这样声明的变量不是响应式的
- 保存前一个值
useCallback
允许你在多次渲染中缓存函数的 React Hook
The
useCallback
Hook only runs when one of its dependencies update. 但是useCallBack的本质工作不是在依赖不变的情况下阻止函数创建,而是在依赖不变的情况下不返回新的函数地址而返回旧的函数地址。不论是否使用useCallBack
都无法阻止组件render
时函数的重新创建使用场景:
- 使用
React.memo
的时候:
Hook 到底是如何工作的
useState
主要是采用了闭包的思想,,闭包通过返回一个函数,可以在函数外部获取到函数内部的变量值,同时也可以保存函数内部的变量值。
简单理解:
- 这样可以实现在函数 useState 外部也能访问到 _val 的值
- 并且使用 setState 后,返回的 _val 仍然是最新值
- 但是这样返回的是一个 getter 函数,跟真正的 useState 有出入
- 直接返回 _val 的话,外部的 state 就会被固定为 initial value 而不是 setState 之后的最新值
改进:
- 闭包里面再返回一个闭包
- useState 也是一个闭包,这样返回的 _val 就始终是 setState 这里传入的最新值,并且是一个值而不是一个 getter 函数
- 使用:
useEffect
简单实现:
多个 hook ⇒ Array
- 这样也可以看出来为什么 hook 只能在组件顶层使用,因为必须要保证每次渲染 hook 调用的顺序都是一致的
- So the basic intuition is having an array of
hooks
and an index that just increments as each hook is called and reset as the component is rendered.
创建自己的hook
封装上面的代码:
然后在一个组件里面调用这个钩子:
那这样的 hook 跟直接创建一个普通的函数有什么区别呢?
- 可以在自定义 hook 中调用别的 hook,但是最好不要在普通的非顶层函数中调用 hook,文档是这样说的:
- 不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的
useState
和useEffect
调用之间保持 hook 状态的正确 - React 内部使用链表结构来存储每个组件的 Hook 状态,每个 Hook 按照调用顺序在链表中占据一个固定位置,如果中间有哪一个 hook 没被调用,后续的 hook 调用就会出问题
- react 判断哪个 state 对应哪个 useState 靠的是 hook 调用的顺序,只要 hook 的调用顺序在多次渲染中保持一致,react 就能将 state 正确地和对应的 hook 进行关联
- 因此不能将 hook 放在分支中,如果需要条件判断也应该在 hook 内部进行
即最好只在 React 的组件函数中调用 hook,但是自定义 hook 中可以调用其他 hook
- 在普通函数当中,其就是一个模块,因此其数据是在所有调用者中共享的,因此我们可以通过一个全局的数据结构来存储数据,这样就可以实现数据的复用
- 自定义 hook 仍然算顶层,React 内部会维护一个调用栈来追踪当前正在执行的组件,在组件的内部 hook 链表中可以找到对应的位置
- 自定义 hook 也是 hook,每次保持在这个组件中的调用顺序,那它内部的 hook 顺序也不变。无论嵌套多少层,只要是在组件渲染期间、按照固定顺序调用的,就是合法的
动画
- 创建动画
- 创建了一个名为
fadeAnim
的Animated.Value
,并放在state
中
- 执行动画
- 将动画跟 View 标签的属性绑定在一起,这个标签的这个属性就是由这个动画控制的
- 在这个例子中:
View
的透明度是和这个值绑定的
生命周期
Class Component 生命周期:
- 挂载阶段:组件实例被创建和插入 dom 树的过程
CompontWillMount
:组件将要渲染,一般不怎么使用了,因为 React 推出了异步渲染导致挂载前的阶段都可以被打断重来CompontDidMount
:组件渲染完毕,在渲染过程中执行一次,一般用于监听事件/请求后台接口
- 更新阶段:主要做状态更新操作
CompontWillReceiveProps
:组件将要接收props数据,一般不用了ShouldComponentUpdate(nextProps, nextState)
:组件接收到新的state或者props,判断是否更新,返回布尔值- 一般用法:通过手动对比 props 和 nextProps、state 和 nextState 来确定是否需要重新渲染子组件。通过实现 shouldComponentUpdate,可以手动控制是否需要重新渲染子组件,从而优化性能。如果不实现该方法,React 会默认返回 true,也就是说每次父组件更新时,子组件都会重新渲染
CompontWillUpdate
:组件将要更新,一般不用了ComponentDidUpdate
:组件已经更新,此时组件已经重新渲染了,在这里可以对组件中的 dom 进行操作,一般用于判断参数是否变化,变化就调用请求函数重新请求数据
- 卸载阶段
ComponentwillUnmount
:组件将要卸载
Function Component 生命周期:
Function Component 是更彻底的状态驱动抽象,甚至没有 Class Component 生命周期的概念,只有一个状态,而 React 负责同步到 DOM。
在 Class Component 中,发请求一般在 ComponentDidMount、ComponentDidUpdate和 ComponentWillUnmount 阶段,但是在函数式组件中,却没有这些,发请求就用 useEffect:它跟 class 组件中的
componentDidMount
、 componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不过被合并成了一个 API- 使用
useEffect
相当于告诉 React 组件需要在渲染后执行某些操作,React 将在执行 DOM 更新之后调用它。
- React 保证了每次运行
useEffect
的时候,DOM 已经更新完毕。
其他
Native Bridge 原生桥接
原生桥接是 React Native 中 JavaScript 代码和原生代码(iOS/Android)之间通信的机制
工厂函数确保每次都能创建新的组件实例,原生桥接负责 JavaScript 和原生代码之间的通信,原生端管理应用的生命周期和系统级功能
闭包陷阱
useState 中的闭包陷阱:
- 主要原因:在 useState 中使用闭包,主要是因为 useState 的参数只会在组件挂载时执行一次。如果我们在 useState 中使用闭包,那么闭包中的变量值会被缓存,这意味着当我们在组件中更新状态时,闭包中的变量值不会随之更新
- 解决:setState 也可以接收一个回调函数作为参数,这个回调函数会接受当前的 state 值作为参数,这样就可以在闭包中使用这个值而不用担心它被缓存
useEffect 中的闭包陷阱:
- 主要原因:在useEffect中使用闭包的问题则是因为useEffect中的函数是在每次组件更新时都会执行一次。如果我们在useEffect中使用闭包,那么这个闭包中的变量值也会被缓存
- 解决:添加依赖数组
React Fiber
解决 CPU 瓶颈和 IO 瓶颈:
- CPU 瓶颈:GUI 渲染线程和 JS 线程是互斥的,这样会导致如果在一帧内 js 脚本执行的时间太长,就没有时间去执行样式布局和样式绘制了,这就导致了掉帧。一般来说,解决掉帧,就是让浏览器在每一帧的时间中都预留一部分时间给 js 执行,然后将控制权交还给渲染线程,让其有足够的时间执行。当预留的时间不够用时,react 会先把控制权给渲染线程,然后在下一帧继续没有完成的工作。
因此要解决 CPU 瓶颈主要是要实现时间片,时间片的关键是将同步更新转换为可中断的异步更新。
- IO瓶颈:IO 的瓶颈主要来自于网络延迟,react 15 及其以下版本中,是使用的一种被称为 Stack Reconciler 的调度算法,当组件的更新任务被调度之后,它会一直执行到更新任务完成,期间不允许其他的任务干扰,这就会导致UI界面可能被卡死
react fiber:
- 可以被理解成一个执行单元/数据结构
- 里面包含了组件的
tag
、key
、type
、stateNode
等相关信息,用于表示组件树上的每个节点和它们的关系
V16
版支持可中断异步更新,任务支持时间切片
- 我们知道
React
在数据更新时会有diff
的操作,此时diff
的过程是被分成一小段一小段的,Fiber节点保存了每一阶段任务的工作进度,js会比较一小部分虚拟dom,然后让出主线程,交给浏览器去做其他操作,然后继续比较,如此循环往复,直至完成diff
,然后一次性更新到视图上
库
lodash
排序:
- orderby(对象数组, ‘按某字段排序’, ‘排序方式(esc/desc)’)
- 返回一个新的数组,不会修改原数组
classnames
可以非常方便地通过条件动态控制class类名的显示

可以用key: value的形式:
- key表示要显示的类名
- value表示控制类名显示的条件
Vue的动态绑定类名和样式:
script:用来控制是否加上active这个类名
redux状态管理
- State 是整个应用在某一时刻的数据快照(由 Redux Store 管理)
- 视图(React)展示 State
- 用户操作或某些事件触发 dispatch(action)
- Redux 的 reducer 根据 action 和旧的 state 生成新的 state
- Redux Store 通知 UI 状态变化
- 视图基于新 state 重新渲染
使用步骤:
- 创建 store,即全局状态容器,里面管理着全局 state
Redux Store = 管理全局状态 + 处理
dispatch
+ 触发 subscribe()
监听更新- provider提供全局状态
这个
ReduxProvider
(来自 react-redux
)把 store 放到 React 的上下文中,这样组件就可以通过 connect()
或 useSelector()
访问到全局状态了- 组件订阅 state + 发出 action
state.counter.num
映射到组件的props.value
dispatch(INCREMENT)
映射到组件的props.onIncrement
(点按钮就能 dispatch)
这个组件通过
connect()
绑定:当组件触发
onIncrement()
,就向 Redux 发出:- Author:orangec
- URL:orange’s blog | welcome to my blog (clovy.top)/article/199c107a-b41d-805f-a76f-d89ebe61ddab
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!