React笔记
React 基础
setState
- state 考虑层级
- state 是不可变数据,不可直接修改
- 使用setState修改state数据
可能是异步,可能会合并?
react <=17 中:
- 组件事件中的setState(第一个参数是对象形式)是异步的(需要在第二个参数回调函数中获取),在setTimeout或自定义事件中是同步的
- setState传入对象会合并(类似Object.assign),传入函数不会合并
react 18中:
- 都是异步更新和state合并,自动批处理 Automatic Batching
propTypes 与 defaultProps 的应用
import PropTypes from 'prop-types'
// PropTypes.arrayOf(PropTypes.string, PropTypes.number) // 数据为数组时,数组的元素可以是什么类型
// PropTypes.oneOfType(PropTypes.string, PropTypes.number) // 数据可以是string,也可以是number
// 设置类型
TodoItem.propTypes = {
test: PropTypes.string.isRequired, // test
content: PropTypes.string,
index: PropTypes.number,
deleteItem: PropTypes.func
}
// 设置默认值
TodoItem.defaultProps = {
test: 'test'
}
React 的生命周期函数
常用生命周期
shouldComponentUpdate:可在此生命周期函数中判断子组件是否需要更新,从而提升性能
shouldComponentUpdate(nextProps, nextState) {
console.log('child', 'shouldComponentUpdate')
if (nextProps.content === this.props.content) {
return false
} else {
return true
}
}
componentDidMount:可在此生命周期函数中发送ajax请求数据
React 中 ref 的使用
1、使用 createRef创建 ref
2、使用函数创建
<input
id="input"
value={this.state.inputValue}
onChange={this.handleInputChange}
ref={(input) => this.input = input}
></input>
React高级内容
受控组件和非受控组件
- 受控组件:受state影响的组件
- 非受控组件:不收state影响的组件,可使用ref、defaultValue、defaultChecked相关属性
场景:富文本组件、上传组件等需要操作dom的组件
无状态组件(函数组件)
- 没有声明周期
- 没有state
- 没有实例
比普通组件性能更好
因为无状态组件只是一个函数,而普通组件是一个class类,需要执行生命周期和render函数
所以,如果组件中只有render函数时,可以使用无状态组件
portals(传送门)
ReactDOM.createPortal
使用场景:
- overflow: hidden
- 父组件 z-index 值太小
- fixed 需要放在 body 第一层
context
将公共信息传递给每个组件
import { Component, createContext } from 'react
const ThemeContext = createContext('light')
// 父级
// ...
return <ThemeContext.Provider value={this.state.theme}>
// ...
</ThemeContext.Provider>
// ...
// 子级(class 组件)
class ThemeButton extends Component {
// static contextType = ThemeContext
render() {
const theme = this.context
return <div>{theme}</div>
}
}
ThemeButton.contextType = ThemeContext
// 函数组件
const ThemeLink = () => {
return <ThemeContext.consumer>
{ value => <p>{value}</p> }
</ThemeContext.consumer>
}
异步组件
- import()
- lazy
- Suspense
souldComponentUpdate
- SCU 默认返回 true
- 必须配合“不可变值”
- 可先不用SCU,有性能问题时再考虑使用
- PureComponent 让class组件默认实现shouldComponentUpdate浅层比较
- memo 函数组件中的 PureComponent
- 浅比较已使用大部分情况,尽量不要使用深度比较
高阶组件 HOC
高阶组件不是一种功能,而是一种模式,实现组件之间的公共逻辑抽离
const HOCFactory = (Component) => {
class HOC extends React.Component {
// 公共逻辑...
render () {
return <Component {...this.props} />
}
}
}
export default HOCFactory(WrapperComponent)
与 render props 相比,模式简单,但会增加组件层级
render 和 props
实现之间的逻辑抽离
class Mouse extends Component {
// 公共逻辑...
render () {
return {this.props.render(this.state)}
}
}
class App extends Component {
render () {
return <Mouse render={(props) => <p>{props.x}{props.y}</p>}>
}
}
与 HOC 相比,代码简洁,学习成本较高
React Hooks
class 组件的问题
- 大型组件很难拆分和重构,很难测试(即 class 不易拆分)
- 相同业务逻辑,分散到各个方法中,逻辑混乱
- 复用逻辑变的复杂,如 Mixins、HOC、 render props
React 组件更易用函数表达
- React 提倡函数式编程,view = fn(props)
- 函数更灵活,更易拆分,更易测试
- 但函数组件太简单,需要增强能力 —— Hooks
Hooks 命名规范
- 规定所有的 Hooks 都用 use 开头,如 useXxx
- 自定义 Hook 也要以 use 开头
- 非 Hooks 的地方,尽量不要使用 useXxx 写法
state Hook 让函数组件实现 state 和 setState
- useState(0) 传入初始值,返回数组 [state, setState]
- 通过 state 获取值
- 通过 setState(1) 修改值
Effect Hook 让函数组件模拟生命周期
- 模拟 componentDidMount、componentDidUpdate - useEffect 无依赖
// 语法:
useEffect(() => {})
- 模拟 componentDidMount - useEffect 依赖 []
// 语法:
useEffect(() => {}, [])
- 模拟 componentDidUpdate - useEffect 无依赖,或者依赖 [a, b]
// 语法1:
useEffect(() => {})
// 语法2:
useEffect(() => {}, [a, b])
- 模拟 componentWillUnMount - useEffect 中返回一个函数
// 语法:
// 模拟 DidMount
useEffect(() => {
// 模拟 WillUnMount
return () => {
// ...组件销毁时的一些逻辑
}
}, [])
模拟 componentWillUnMount 但不完全相等
- useEffect 依赖 [],组件销毁是执行fn,等于 WillUnMount
- useEffect 无依赖,或者依赖 [a, b],组件更新时也会执行 fn
- 即,下一次执行 useEffect 之前,就会执行 fn,无论更新或卸载
useEffect 让纯函数有了副作用
- 默认情况下,执行纯函数,输入参数,返回结果,无副作用
- 所谓副作用,就是对函数之外造成影响,如设置全局定时任务
- 而组件需要副作用(技术对业务妥协),所以需要 useEffect “钩”入纯函数中
其他 Hook
ref Hook
const btnRef = useRef(null) // 初始值
useEffect(() => {
console.log(btnRef.current) // dom节点
})
return <button ref={btnRef}>按钮</button>
context Hook
import { Component, createContext, useContext } from 'react
const ThemeContext = createContext('light')
// 父级
// ...
return <ThemeContext.Provider value={this.state.theme}>
// ...
</ThemeContext.Provider>
// ...
// 函数组件
const ThemeLink = () => {
const theme = useContext(ThemeContext)
return <p>{theme}</p>
}
reducer Hook
const [state, dispatch] = useReducer(reducer, initialState)
useReducer 和 redux 的区别
- useReducer 是 useState的代替方案,用于 state 复杂变化
- useReducer 是单个组件状态管理,组件通讯还需要props
- redux 是全局的状态管理,多组件共享数据
memo Hook
- useMemo 缓存数据
// 子组件用 memo 包裹
const Child = memo(() => {})
// 父组件
const App = () => {
const [name, setName] = useState('zhangsan')
// useMemo 缓存数据
const userInfo = useMemo(
() => ({name, age: 20}),
[name]
)
return <Child userInfo={userInfo} />
}
callback Hook
- useCallback 缓存函数
// 子组件用 memo 包裹
const Child = memo(() => {})
// 父组件
const App = () => {
const [name, setName] = useState('zhangsan')
// useMemo 缓存数据
const userInfo = useMemo(
() => ({name, age: 20}),
[name]
)
// useCallback 缓存函数
const handleChange = useCallback(() => {
// ...
}, [])
return <Child userInfo={userInfo} handleChange={handleChange} />
}
自定义 Hook
- 本质是一个函数,以 use 开头(重要)
- 内部正常使用 useState useEffect 或其他 Hooks
- 自定义返回结果,格式不限
- 使用第三方 hooks
组件逻辑复用
class组件逻辑复用有哪些问题
Mixins 的问题
- 变量作用域来源不清
- 属性重名
- Mixins 引入过多会导致顺序冲突
高阶组件 HOC
- 组件层级嵌套过多,不易渲染,不易调试
- HOC 会劫持 props,必须严格规范,容易出现疏漏
Render props
- 学习成本高,不易理解
- 只能传递纯函数,而默认情况下纯函数功能有限
Hooks 做组件逻辑复用的好处
- 完全符合 Hooks 原有规则,没有其他要求,易理解记忆
- 变量作用域明确
- 不会产生组件嵌套
规范和注意事项
Hooks 使用规范
- 命名规范 useXxx !!!
- Hooks 使用规范!!!
只能用于 React 函数组件和自定义 Hook 中,其他地方不可以
只能用于顶层代码,不能在循环、判断中使用 Hooks
eslint 插件 eslint-plugin-react-hooks 可以帮到我们 - Hooks 的调用顺序
无论是 render 还是 re-render ,Hooks 调用顺序必须一致
如果 Hooks 出现循环、判断里,则无法保证顺序一致
Hooks严重依赖于调用顺序!重要!
Hooks 注意事项
- useState 初始化值,只有第一次有效
如果 useState 传入的是动态值,第二次赋值时无效 - useEffect 内部不能修改state
解决办法:
1、第二参数不传入依赖或者传入修改的那个state;
2、可以借助 useRef
3、可以借助临时变量,在useEffect 外面定义一个变量做转换 - useEffect可能出现死循环
第二个值传入的是引用类型的依赖时,会出现死循环,因为 useEffect 内部使用 Object.is 来判断的,引用类型相等时也为false;
解决办法:将引用类型的数据打散后作为依赖
React 原理
函数式编程
- 一种编程范式,概念比较多
- 纯函数(返回一个新值)
- 不可变值(setState和redux中都要使用不可变值)
vdom 和 diff
- h函数
- vnode 数据结构
- patch函数
- 只比较同一层级,不跨级比较
- tag不相同,则直接删掉重建,不做深度比较
- tag和key都相同,则认为相同节点,不做深度比较
JSX的本质
React.createElement(tag/component, props/null, [child, child]/child, child),类似vue的h函数,执行后返回vnode,执行patch函数生成dom
React的合成事件机制
- 所有事件挂在到 root上
- event 原生的,是 SyntheticEvent 合成事件对象(event.nativeEvent 是原生事件对象)
- 和 vue 事件不同,和 DOM 事件也不同
为何要合成事件机制?
- 更好的兼容性和跨平台
- 载到 root ,减少内存消耗,避免频繁解绑
- 方便事件的统一管理(如事务机制)
更新:
- React 16 事件绑定到 document 上
- React 17 事件绑定到root组件
- 有利于多个 React 版本并存,列如微前端
setState 和 batchUpdate
- setState 主流程
- batchUpdate 机制
- transaction 事务机制
setState 是异步还是同步?
- setState 无所谓异步还是同步
- 看是否能命中 batchUpdate 机制
- 判断 isBatchingUpdates
transaction 事务机制
- 定义开始逻辑和结束逻辑
- 执行开始逻辑 -> 执行函数 -> 执行结束逻辑
组件渲染和更新过程
组件渲染过程
- props state
- render() 生成 vnode
- patch(elm, vnode)(不一定叫patch这个名字)
组件更新过程
- setState(newState) --> 生成 dirtyComponents(可能有子组件)
- 遍历所有的 dirtyComponents ,根据新的props和state,执行 render 生成 newVnode
- patch(vnode, newVnode)
React fiber 如何性能优化
path 更新的两个阶段
- reconciliation 阶段-执行 diff 算法,纯 js 计算
- commit 阶段-将 diff 结果渲染 DOM
可能会有性能问题
- JS是单线程,切和DOM渲染共用一个线程
- 当组件足够复杂,组件更新时计算和渲染都压力大
- 同时再有DOM操作需求(动画,鼠标拖拽等),将卡顿
解决方案 fiber
- 将 reconciliation阶段进行任务拆分
- DOM 需要渲染时暂停,空闲时恢复
- window.requestIdleCallback 可以知道浏览器需要做DOM渲染,该Api不是所有浏览器都支持,不支持执行就是会慢一点
redux 入门
核心API
createStore:创建store
store.dispatch:派发action
store.getState:获取store所有的数据
store.subscribe:订阅store的改变,store发生改变,subscribe中接受的回调函数就会被触发
注:
reducer 可以接受state,但是绝不能修改state
reducer 必须是纯函数,给定固定的输入,就一定会有固定的输出,而且不会有任何副作用
单向数据流
redux 进阶
redux-thunk
中小型项目中使用,异步请求或者非常复杂的逻辑,可以放到redux-thunk中统一管理
可以在action中写函数
redux-saga
大型项目中可使用该库进行action管理
react-redux
在react组件中方便操作store
React 周边
immutable、redux-immutable
管理store中的数据,防止不小心直接在reducer中改变state的值(彻底拥抱“不可变值”)
如果使用pureComponent需要使用immutable管理数据
Api:
- Map
- set
- get
- getIn
styled-components
在react中编写样式,区分在组件中生效还是全局生效
react-router、react-router-dom
路由
react-transition-group
动画库
React 性能优化
- 渲染列表时加 key
- 自定义事件、DOM事件及时销毁
- 合理使用异步组件
- 减少函数 bind this 的次数
- 合理使用 SCU PureComponent 和 memo
- 合理使用 immutable.js
- webpack 层面的优化
- 前端通用的性能优化,如图片懒加载
- 使用 SSR
React 和 Vue 的区别
相同点
- 都支持组件化
- 都是数据驱动视图
- 都使用 vdom 操作 DOM
不同点
- React 使用 JSX 拥抱JS,Vue 使用模版拥抱 html
- React 是函数式编程,Vue 是声明式编程
- React 更多需要自力更生,Vue 把想要的都给你