创建React项目
使用React脚手架create-react-app
create-react-app test-react

React开发依赖
- react:包含react所必须的核心代码
- react-dom:react渲染在不同平台所需要的核心代码
- babel: 将jsx转换成react代码的工具(因为使用用React.createElement编写代码可读性太差)
JSX
JSX本质
jsx 仅仅只是React.createElement(component, props, ...children) 函数的语法糖。所有的jsx最终都会被转换成React.createElement的函数调用。

JSX使用
JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素
- 特性一: 当变量是Number,String,Array类型时, 可以直接显示
- 特性二: 当变量是null,undefined,boolean类型时, 内容为空
- 特性三:Object对象类型不能作为子元素(not valid as a React child)
Hello React
<script type="text/babel">
// 1. 定义变量
let message = "Hello World"
const btnClick = () => {
message = "hello react"
render()
}
// 2.渲染内容
const root = ReactDOM.createRoot(document.querySelector("#app"))
render()
function render(){
root.render((
<div>
<h2>{message}</h2>
<button onClick={btnClick}>修改文本</button>
</div>
))
}
</script>
class组件化开发
- 定义一个类(继承自React.Component)
- 实现当前组件的reader函数
- 在构造函数中 this.state= { 定义数据 }, 当我们数据发生变化时,可以调用this.setState来更新数据,并且通知React进行update(重新调用reader函数)操作;
如果不初始化state或不进行方法绑定(为事件绑定实例this),则不需要为react组件实现构造函数 - render函数的返回类型
- react元素
- 数组或fragments:使得reader方法可以返回多个元素
- Portals:可以渲染子节点到不同的DOM子树中.
- 字符串或者数值类型:他们在dom中会被渲染为文本节点
- 布尔类型或null:什么都不渲染.
class App extends React.Component {
constructor(){
super();
this.state = {
message:"heool world"
}
},
reader(){
return <h2>hello world</h2>
}
}
const root = ReactDom.createRoot(document.querySelector("#root"))
root.render(<App/>)
class组件-事件绑定
默认情况下在类中直接定义一个函数,并且将这个函数绑定到元素的onClick事件上,当前这个函数的this指向的是undefined, 因为为React并不是直接渲染成真实的DOM,我们所编写的button只是一个语法糖,它的本质React的Element对象;那么在这里发生监听的时候,react在执行函数时并没有绑定this,默认情况下就是一个undefined;
解决办法
<button onClick={this.btnClick.bind(this)}>修改文本</button><button onClick={()=>this.btnClick}>修改文本</button>- class field特性 在class中定义一个btnClick=()=>{}
class App extends React.Component {
// 类字段
btnClick= ()=>{},
constructor(){
super();
this.state = {
message:"heool world"
}
},
reader(){
return <h2>hello world</h2>
}
}
函数组件
函数组件是使用function来进行定义的函数,只是这个函数会返回和类组件中render函数返回一样的内容. 特点
- 没有生命周期,也会被更新并挂载,但是没有生命周期函数
- this关键字不能指向组件实例(因为没有组件实例)
- 没有内部状态(state)
export default function App(){
return (
<div>hello world</div>
)
}
认识类组件的生命周期
- componentDidMount函数: 组件已经挂载到Dom上时回调
- 此时可以操作dom, 发送网络请求
- componentDidUpdate函数: 组件已经发生了更新时回调
- componentWillUnmount函数:组件即将移除时回调;
- getDerivedStateFromProps:state的值在任何时候都依赖于props时使用;该方法返回一个对象来更新state;
- getSnapshotBeforeUpdate:在react更新dom之前回调的一个函数, 可以获取DOm更新前的一些信息(比如滚动位置)
- shouldComponentUpdate:该生命周期函数很常用.

shouldComponentUpdate
shouldComponentUpdate是决定当前组件是否执行render函数的‘开关’,这个函数有三个参数, 默认返回的是true
- nextProps 修改之后最新的props属性
- nextState 修改之后最新的state属性
- nextContext 最新的this.context
PureComponent
如果所有的类组件,我们都要手动实现SCU, 那么开发者会非常累;事实上React已经考虑到了这一点,所以React已经默认帮我们实现好了。PureComponent内部会对新旧props,state进行浅层对比, 如果不一样就会返回true;
注意: 不可以直接修改state中的引用类型变量内的值, 浅层比较是无法对比出来正确结果的
memo
那么如果是函数式组件,没有生命周期怎么办呢? React已经帮我们处理好了,他提供了一个memo的高阶函数;memo高阶组件包裹起来的组件有对应的特点:只有props发生改变时,才会重新渲染.
组件通信
父传子
- 父组件通过
属性=值的形式来传递给子组件数据; - 子组件通过
props参数获取父组件传递过来的数据;
子传父
- 利用父亲传过来的函数props 调用函数实现传递数据

参数propTypes
官网 默认值使用组件.defaultProps

React中的"插槽"
- 组件的children子元素
2. props属性传递React元素

Context应用场景
共享对于一个组件树而言是全局的数据,例如用户,主题,语言;
- React.createContext(defaultValue)
- 创建一个需要共享的context对象;
- Class.contextType
- 挂载在class上的contextType属性会被重赋值为一个由React.createContext()创建的Context对象;
- 这能让你使用this.context来消费最近的Context上的值
- 如果一个组件订阅了context, 那么这个组件会从离自身最近匹配的provider中读取到当前得context值
- defaultValue是组件在顶层查找过程中没有找到对应的provider,那么就使用默认值

- Context.Provider
- 每个context对象都会返回一个Provider React组件,它允许消费组件订阅context变化;
- Provider接收一个value属性,传递给消费组件
- 一个Provider可以和多个消费组件有对应关系;
- 多个Provider也可以嵌套使用,里层的会覆盖外层的数据;
- 当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染;

- Context.Consumer 一般用于函数组件或者需要使用多个Context时

React数据管理和界面渲染
我们并不能直接通过修改state的值来让界面发生更新,因为React并不知道数据发现了改变; React中没有类似vue2中的object.defineProperty来监听数据的变化; 我们必须通过setState来告知React数据已经发生了变化;
setState
用法
- 基本用法
this.setState({
name:'zebluo'
})
- 传递一个回调函数, 可以在回调函数中编写新的state的逻辑, 当前的回调函数会将之前的state,props传递进来
this.setState((state,props)=>{
// 可以编写新的state逻辑
// 可以接收之前state和props
return {
name:'zebluo'
}
})
- setStat第二个参数 callback
this.setState({
name:'zebluo'
}, ()=>{
// setState是一个异步调用,如果希望数据更新之后,获取到对应结果执行一些逻辑代码那么就可以使用callback回调
console.log('this.state.name', this.state.name)
})
为什么setState设计为异步?
- 显著提升性能。如果每次调用都更新,那么render函数会被频繁调用界面重新渲染, 效率会很低;
- 如果同步更新state, 但是还没有执行reader函数,那么state和props不能保持同步; 如果state和props不能保持一致性,会产生很多问题
React18之前
setState在React18之前,其实分成两种情况;
- 在组件生命周期或React合成事件中,setState是异步的;
- 如果是在原生JS的异步事件中, 例如setTimeout或者原生dom事件,setState是同步的;
强制setState同步获取
flushSync(()=>{
this.setState({name:'zebluo'})
})
console.log(this.state.name)
ref
获取Dom
- 传入字符串, 使用时通过this.refs.xxx获取对应元素
- 传入一个对象
- 对象是通过React.createrRef()方式创建出来的;
- 使用时获取到创建的对象其中有一个current属性就是对应的元素;
- 传入一个函数
- 该函数会在dom被挂载时进行回调,这个函数会传入一个元素对象,我们可以自己保存;
ref的类型
- ref的值根据节点类型而有所不同:
- 当ref属性用于html元素时, ref接收dom元素作为其current属性;
- 当ref属性用于自定义class组件时, ref对象接收组件的挂载实例作为其current属性;
- 不能在函数组件上使用ref,因为它没有实例;
函数组件可以通过React.forwardRef来绑定内部的元素
受控组件
在React中,HTML表单的处理方式和普通的DOM元素不太一样:表单元素通常会保存在一些内部的state。 React中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用setState()来更新。
- 使React的state成为“唯一数据源”
- 渲染表单的React 组件还控制着用户输入过程中表单发生的操作;
- 被React 以这种方式控制取值的表单输入元素就叫做“受控组件”;
所以我们可以简单理解为一旦在表单元素上设置了value属性为this.state中的值时,此时该组件就为受控组件。
非受控组件
给表单元素设置defalutValue(defaultChecked)默认值, 再通过ref获取dom里的值
高阶组件
高级组件和高阶函数非常相似。那么什么是高阶函数呢? 接受一个或者多个函数作为输入, 输出一个函数的函数就是高阶函数。
那么什么是高阶组件HOC
高阶组件是参数为组件,返回值为新组件的函数 解析:首先高阶组件本身不是一个组件,而是一个函数, 其次这个函数的参数是一个组件,返回值也是一个组件
props的增强
- 不修改原有代码的情况下,添加新的props
2. 利用高阶组件来共享Context
渲染判断鉴权
生命周期劫持
高阶函数的意义
我们会发现利用高阶组件可以针对某些React代码进行更加优雅的处理; 其实早期React提供组件之前的复用方式是mixin目前已经不再建议使用;原因是
- Mixin会互相依赖,互相耦合, 不利于维护
- 不同Mixin中的方法可能会冲突;
- Mixin多时,组件处理起来会比较麻烦,甚至还要为其做相关处理; HOC也有自己的缺点
- HOC需要在原有组件上进行包裹或者嵌套, 如果大量使用HOC,将会产生非常多的嵌套, 这让调试非常困难;
- HOC可以劫持props, 在不遵守约定的情况下也可能造成冲突;
Portals的使用
某些情况下,我们希望渲染的内容独立于父组件,甚至是独立于当前挂载到的DOM元素中(默认都是挂载到id为root的DOM 元素上的); Portal 提供了一种将子节点渲染到存在于父组件以外的DOM 节点的优秀的方案:
- 第一个参数(child)是任何可渲染的React 子元素,例如一个元素,字符串或fragment;
- 第二个参数(container)是一个 DOM 元素;
ReactDOM.createPortal(child, container);
fragment
在之前的开发中,我们总是在一个组件中返回内容时包裹一个div元素, 我们又希望可以不渲染这样一个div应该如何操作呢?
当然是使用Fragment,它允许你将子列表分组,而无需向dom添加额外节点;React还提供了Fragment的短语法, 他看起来像是空标签<></>;但是,我们需要在Fragment中添加key,那么就不能使用短语法
StrictMode
StrictMode 是一个用来突出显示应用程序中潜在问题的工具:
- 与Fragment 一样,StrictMode 不会渲染任何可见的 UI;
- 它为其后代元素触发额外的检查和警告;
- 严格模式检查仅在开发模式下运行;它们不会影响生产构建;
严格模式检查的是什么?
- 识别不安全的生命周期;
- 使用过时的ref API
- 检查意外的副作用
- 组件的constructor会被调用两次
- 这是严格模式下故意进行的操作,让你来查看在这里写的一些逻辑代码被调用多次时,是否会产生一些副作用;
- 在生产环境中,是不会被调用两次的;
- 使用废弃的findDOMNode方法
- React API中,可以通过findDOMNode来获取DOM,不过已经不推荐使用了。
- 检测过时的context API
- 早期的Context是通过static属性声明对象属性,通过getChildContext返回Context对象等方式来使用Context的;
过渡动画
暂无
React中的css
官方依然是希望内联合适和普通的css来结合编写;
普通的css
普通的css我们通常会编写到一个单独的文件,之后再进行引入。
缺陷
- 组件化开发中我们总是希望组件是一个独立的模块,即便是样式也只是在自己内部生效,不会相互影响
- 这种编写方式最大的问题是样式之间会相互层叠掉
css modules
css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。React的脚手架已经内置了css modules的配置:.css/.less/.scss 等样式文件都需要修改成 .module.css/.module.less/.module.scss 等;
缺陷:
- 引用的类名,不能使用连接符(.home-title),在JavaScript中是不识别的;
- 所有的className都必须使用{style.className} 的形式来编写;
- 不方便动态来修改某些样式,依然需要使用内联样式的方式
CSS in JS
“CSS-in-JS” 是指一种模式,其中 CSS 由JavaScript 生成而不是在外部文件中定义;
在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
styled-components
styled-components的本质是通过函数的调用,最终 创建出一个组件:
- 这个组件会被自动添加上一个不重复的class;
- styled-components会给该class添加相关的样式;
- 它支持类似于CSS预处理器一样的样式嵌套
props,attrs属性
<MYInput type="password" left="20px" />
props可以被传递给styled组件
- 获取props需要通过${}传入一个插值函数,props会作为该函数的参数;
- 这种方式可以有效的解决动态样式的问题;
使用attrs属性添加props默认值
styled高级特性
React中添加class classnames(第三方库)
纯函数
函数式编程中有一个非常重要的概念叫纯函数,JavaScript符合函数式编程的范式,所以也有纯函数的概念;
- 此函数在相同的输入值时,需产生相同的输出。
- 函数的输出和输入值以外的其他隐藏信息或状态无关,也和由I/O设备产生的外部输出无关。
- 该函数不能有语义上可观察的函数副作用,诸如“触发事件”,使输出设备输出,或更改输出值以外物件的内容等。
人话总结就是:
确定的输入,一定会产生确定的输出;函数在执行过程中,不能产生副作用;
React中就要求我们无论是函数还是class声明一个组件,这个组件都必须像纯函数一样,保护它们的props不被修改
redux
当应用程序复杂时,state在什么时候,因为什么原因而发生了变化,发生了怎么样的变化,会变得非常难以控制和追踪;状态之间相互会存在依赖,一个状态的变化会引起另一个状态的变化,View页面也有可能会引起状态的变化;
- Redux就是一个帮助我们管理State的容器:Redux是JavaScript的状态容器,提供了可预测的状态管理;
- Redux除了和React一起使用之外,它也可以和其他界面库一起来使用(比如Vue),并且它非常小(包括依赖在内,只有2kb)
三大原则 - 单一数据源(整个应用程序的state被存储在一颗object tree中)
- State是只读的
- 使用纯函数来执行修改
- 通过reducer将 旧state和 actions联系在一起,并且返回一个新的State:
- 随着应用程序的复杂度增加,我们可以将reducer拆分成多个小的reducers,分别操作不同state tree的一部分;
- 但是所有的reducer都应该是纯函数,不能产生任何的副作用;
Redux使用方式
Redux的核心理念- action
Redux的核心理念- reducer
react-redux使用
源码
redux-thunk
默认情况下的dispatch(action),action需要是一个JavaScript的对象,redux-thunk可以让dispatch(action函数),action可以是一个函数,该函数会被调用,并且会传给这个函数一个dispatch函数和getState函数;;
Redux Toolkit
- configureStore:包装createStore以提供简化的配置选项和良好的默认值。它可以自动组合你的 slice reducer,添加你提供 的任何Redux 中间件,redux-thunk默认包含,并启用 Redux DevTools Extension。
- createSlice:接受reducer函数的对象、切片名称和初始状态值,并自动生成切片reducer,并带有相应的actions。
- createAsyncThunk: 接受一个动作类型字符串和一个返回承诺的函数,并生成一个pending/fulfilled/rejected基于该承诺分 派动作类型的thunk
Redux Toolkit的异步操作
Redux Toolkit的数据不可变性
算法:Persistent Data Structure(持久化数据结构或一致性 数据结构)
- 用一种数据结构来保存数据;
- 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会 对内存造成浪费;
Redux Hooks
在Redux7.1开始,提供了Hook的方式,我们再也不需要编写connect以及对应的映射函数了
- useSelector的作用是将state映射到组件中
// 参数一:将state映射到需要的数据中
// 参数二:可以进行比较来决定是否组件重新渲染, 默认会比较我们返回的两个对象是否完全相等; 如果我们传入一个shallowEqual浅层比较函数,就可以进行性能优化, 只会针对我们本次映射的数据和上次映射出来的数据进行比较;
const {count } = useSelector((state)=>({
count:state.counter.count
}), shallowEqual)
- useDispatch非常简单,就是直接获取dispatch函数,之后在组件中直接使用即可;
- 可以通过useStore来获取当前的store对象;
之前的使用方式:
现在:
React Hooks
Hook是React 16.8 的新增特性,它可以让我们在不编写class的情况下使用state以及其他的React特性(比如生命周期);ps:Hook只能在函数组件中使用,不能在类组件,或者函数组件之外的地方使用;且只能在函数最外层调用Hook。不要在循环、条件判断或者子函数中调用
- class组件相对于函数式组件有什么优势?
- class组件可以定义自己的state,用来保存组件自己内部的状态;
- class组件有自己的生命周期,我们可以在对应的生命周期中完成自己的逻辑;
- class组件可以在状态改变时只会重新执行render函数以及我们希望重新调用的生命周期函数componentDidUpdate等
所以,在Hook出现之前,对于上面这些情况我们通常都会编写class组件
- class组件存在的问题
- 复杂组件难以理解
- 组件复用状态很难
useState
- useState会帮助我们定义一个state变量,useState 是一种新方法,它与 class 里面的this.state 提供的功能完全相同。
- 一般来说,在函数退出后变量就会”消失”,而state 中的变量会被React 保留。
- useState接受唯一一个参数,在第一次组件被调用时使用来作为初始化值。(如果没有传递参数,那么初始化值为undefined))如果参数是函数,会被立即执行,并且用函数返回值作为初始化值;
useEffect
- 通过useEffect的Hook,可以告诉React需要在渲染后执行某些操作
- useEffect要求我们传入一个回调函数,在React执行完更新DOM操作之后,就会回调这个函数
- 默认情况下,无论是第一次渲染之后,还是每次更新之后,都会执行这个回调函数;
要清除Effect
useEffect(()=>{
console.log('监听')
return ()=>{
console.log('取消监听')
}
})
useEffect传入的回调函数A本身可以有一个返回值,这个返回值是另外一个回调函数B, 这是回调函数B是effect 可选的清除机制,每个effect 都可以返回一个清除函数;React 会在组件更新和卸载的时候执行清除操作;
Effect性能优化
某些代码我们只是希望执行一次即可,类似于componentDidMount和componentWillUnmount中完成的事情;(比如网 络请求、订阅和取消订阅) useEffect实际上有两个参数:
- 执行的回调函数
- 该useEffect在哪些state发生变化时,才重新执行;(受谁的影响, 但是如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组[], 如果不传就每次创建和更新都会执行)
let [count, setCount] = useState(0)
useEffect(()=>{
console.log('监听')
return ()=>{
console.log('取消监听')
}
}, [count])
useContext
我们要在组件中使用共享的Context有两种方式
- 类组件可以通过类名.contextType = MyContext方式,在类中获取context;
- 多个Context或者在函数式组件中通过MyContext.Consumer 方式共享context;
useReducer
useReducer仅仅是useState的一种替代方案;在某些场景下,如果state的处理逻辑比较复杂,我们可以通过useReducer来对其进行拆分。
useCallback
useCallback实际的目的是为了进行性能的优化。useCallback会返回一个 memoized(记忆的)函数;在依赖不变的情况下,多次定义的时候,返回的值是相同的;useCallback的目的是不希望子组件进行多次渲染,并不是为了函数进行缓存;
// 当count变化时才会返回一个新函数
const changeCount = useCallback(()=>{
setCount(count+1)
}, [count])
useMemo
useMemo实际的目的也是为了进行性能的优化。useMemo返回的也是一个memoized(记忆的)值;在依赖不变的情况下,返回的值是相同的;
- 进行大量的计算操作,避免每次渲染时都重新计算;
- 对子组件传递相同内容的对象时,使用useMemo进行性能的优化, 避免在父组件重新渲染时重新定义对象导致传给子组件的对象发现了变化从而也重新渲染;
空数组[]: 只在初次渲染时计算一次,之后不再重新计算,适用于只需要在初始化时计算的场景。
省略第二个参数: 每次渲染都会重新计算,基本等同于没有使用useMemo。
const caclCount = useMemo(()=>{
return 1000*200*300*count
}, [count])
useRef
useRef返回一个ref对象,返回的ref对象再组件的整个生命周期保持不变;
常用用法
- 引入DOM(或者组件,必须要是class组件)元素
- 保存一个数据,这个对象在整个生命周期中可以保存不变;
1.
const titleRef = useRef()
<div ref={titleRef}></div>
2.
const count = useRef(initValue)
// count.current获取
解析:因为useCallback依赖空数组,整个生命周期不会改变, 所以如果不用countRef 而是直接使用count+1, 函数中绑定的count永远都会是初始值, 而countRef因为整个生命周期也是保持不变的,每次count改变时又都会重新被赋值 所以不会出现闭包陷阱;
useImperativeHandle
通过forwardRef可以将ref转发到子组件;子组件拿到父组件中创建的ref,绑定到自己的某一个元素中;forwardRef的做法本身没有什么问题,但是我们是将子组件的DOM直接全部暴露给了父组件;通过useImperativeHandle可以只暴露固定的操作
useImperativeHandle(ref, ()=>{
return {
// 外层只能通过ref获取到return对象内的值或者方法
}
})
useLayoutEffect
useLayoutEffect看起来和useEffect非常的相似,事实上他们也只有一点区别而已:
- useEffect会在渲染的内容更新到DOM上后执行,不会阻塞DOM的更新;
- useLayoutEffect会在渲染的内容更新到DOM上之前执行,会阻塞DOM的更新;
如果我们希望在某些操作发生之后再更新DOM,那么应该将这个操作放到useLayoutEffect
自定义Hook
自定义Hook本质上只是一种函数代码逻辑的抽取,严格意义上来说,它本身并不算React的特性。它必须以use开头。
useId
useId是一个用于生成横跨服务端和客户端的稳定的唯一ID 的同时避免hydration 不匹配的hook。
- useId 是用于同构应用开发的,前端SPA页面并不需要它;
- useId可以保证应用程序在客户端和服务器端生成唯一的ID,这样可以有效的避免通过一些手段生成的id不一致,造成 hydration mismatch;
useTransition
返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。它其实在告诉react对于某部分任务的更新优先级较低,可以稍后进行更新。
useDeferredValue
useDeferredValue 接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。
react-router6
npm install react-router-dom
路由配置和跳转
通常路径的跳转是使用Link组件,最终会被渲染成a元素, NavLink是在Link基础之上增加了一些样式属性
NavLink(默认匹配成功就会添加上一个动态的activeclass), 也支持传入style和className, 传入函数,函数接受一个对象,包含isActive属性
Navigate导航
Navigate用于路由的重定向,当这个组件出现时,就会执行跳转到对应的to路径中
// 匹配到/的时候,直接跳转到/home页面
<Route path='/' element={<Navigate to="/home">} />
Not Found页面
// 设置path为* 如果没有匹配到就会跳转notFound
<Route path="*" element={<NotFound/>} />
路由嵌套
组件用于在父路由元素中作为子路由的占位元素。
手动路由的跳转
在Router6.x版本之后,代码类的API都迁移到了hooks的写法:
- 如果我们希望进行代码跳转,需要通过useNavigate的Hook获取到navigate对象进行操作;
- 么如果是一个函数式组件,我们可以直接调用; 如果是个类组件,需要使用高阶函数的方式实现;
封装一个高阶组件withRouter
路由参数传递
传递参数有二种方式:
- 动态路由的方式
- search传递参数
动态路由的概念指的是路由中的路径并不会固定:- 比如/detail的path对应一个组件Detail;
- 如果我们将path在Route匹配时写成/detail/:id,那么 /detail/abc、/detail/123都可以匹配到该Route,并且进行显示;