提示:文章仅仅是用来记录学习总结,会有很多其他文章借鉴的地方,还望见谅!
文章目录
前言—> React学习总结
React,用于构建用户界面的 JavaScript 库,提供了 UI 层面的解决方案,遵循组件设计模式、声明式编程范式和函数式编程概念,以使前端应用程序更高效,使用虚拟DOM来有效地操作DOM,遵循从高阶组件到低阶组件的单向数据流,帮助我们将界面成了各个独立的小块,每一个块就是组件,这些组件之间可以组合、嵌套,构成整体页面。
提示:以下是本篇文章正文内容,下面案例可供参考
一、JSX
JSX 是JavaScript XML的缩写,不是html或xml,基于ECMAScript的一种新特性,一种定义带属性树结构的语法;
- jsx元素写法 jsx形式、React.createElement的形式
- jsx元素本质是一个对象,这个对象是不可变的对象—因为jsx元素最终需要被渲染成真实的DOM元素 所以
不能去修改
只能去使用 - 由class或function定义出来的叫组件,
组件名必须大写
- 由jsx语法或React.createElement返回的结果叫jsx元素,jsx元素充当组件的视图模板
- 在jsx元素中,你要嵌套表达式,必须使用{}包起来,也就是说,在jsx元素中,看到{}你要知道里面放的都是表达式。任何有值的内容都是表达式。
- jsx也可以做了函数的入参,也就是在调用一个函数时,传递传递一个jsx元素,也可以做为函数的返回值,还可以用在if或for中。
- class 变成了 className、 for 变成了 htmlFor、tabindex 变成了tabIndex
- jsx元素中 新增了的属性 key 用于列表渲染、ref 方便DOM操作、dangerouslySetInnerHTML 用来渲染一片html字符串
- jsx只能有
一个根标签
- 在jsx元素,不仅组件可以使用单标签,任何html标签都可以使用单标签
- JSX语法中是支持点语法的
React.Component React.Fragment Ml.Button Ml.Form
- 对于组件来说,
props就是自定义属性
,对于html标签来说,props是我们之前学习过的html标签的属性,props和vue一样,只能使用它,不要去修改它。
- 在使用组件时,如果组件使用双标签,在双标签之间就可以写内容,在
双标签之间写的内容是通过props.children来接收
的,props.children可以是任意类型的数据,基本数据类型可以,引用数据类型也可以,也可以是函数。 在使用JSX时,如果是数组,可以直接渲染
- 如果数组中有boolean值,有null,有und,会被直接忽略,也就是说不会生成对应的文本节点。
二、react状态
1.类组件中状态使用
- 在类组件中,不推荐直接修改状态,因为修改状态的目的还需要让模板刷新, 有个专属API
setState
是专门来修改状态的 this.setState
是异步的,在React18中,永远是异步的,在React18之前,有可能是同步,有可能是异步this.setState({}, callback)
在修改状态时,当新值与旧值无关时,推荐使用这种写法,callback
表示当状态修改后,自动执行,当状态修完后,有一些业务逻辑放到callback
中。this.setState((state, props)=>({}), callback)
当新值与旧值有关时,新值由旧值计算而来,形参state
永远表示旧值,建议使用这种写法。callback
表示当状态修改后,自动执行,当状态修完后,有一些业务逻辑放到callback
中。
2.事件绑定 改变this的指向
- 利用ES5中的bind手动绑定this
- 利用ES6的箭头函数自动绑定this
3.状态在函数组件中的使用
函数组件中是不能定义状态,但是配合hook可以定义状态,hook类似于vue3中的组合式API,在React16.8中就有了
- 在V18之前,在合成事件中是异步的,在宏任务和promise.then中是同步的。
- 在V18中,无论在哪里都是异步的。
三、react 类组件声明周期(钩子函数)
只有类组件才会有生命周期钩子函数,函数式组件没有生命周期钩子的函数的。
生命周期函数很多,需要掌握6个:
装载阶段(3个):constructor, render, componentDidMount
更新阶段(2个):render,componentDidUpdate
卸载阶段(1个):componentWillUnmount
一、初始渲染阶段:这是组件即将开始其生命之旅并进入 DOM 的阶段。
1.constructor(装载)
在constructor
中,不能调用接口,不能进行DOM操作,不能开定时器… 一切和业务相关的代码都不能写在constructor`钩子函数中。
-
在
constructor
中,可以做什么:定义状态
绑定方法的this
2.render(渲染)
作用:用于返回组件的视图结构 , 这个结构在vue中叫模板 在react中叫jsx
。它在背后生成一颗Fiber树
在vue中叫虚拟DOM在React叫Fiber树
,早期的React也是使用的虚拟DOM。
render
是一个函数,这个函数返回一个jsx
,jsx
叫jsx元素
,本质是一个对象,创建jsx
元素有2种方法,一种是直接使用jsx
,另外一种是React.creeateEleement
。也就说,调用render函数
,会生成棵Fiber树
,类似于Vue中的虚拟DOM
,这个Fiber树是双向链表结构
。生成这课Fiber树的过程是异步的,生成的过程是可以中断,能不能中断是看浏览器忙不忙,如果浏览器比较忙,就可能中断,等待浏览器不忙时,会继续生成,直到Fiber树创建完成。然后会进行协调运算,这个协调运算类似于Vue中的Diff运算,也就是一棵老的Fiber树和一棵新的Fiber树进行对比运算
。运算对比后,就会进入到commmit提交阶段
,一次性提交Fiber更新DOM
。
3.componentDidMount(装载之后)
类似于vue中的mounted,表示页面第一次渲染完成。
- 调用接口
- 开定时器
- DOM操作
- 编写业务逻辑
二、更新阶段:一旦组件被添加到 DOM,它只有在 prop 或状态发生变化时才可能更新和重新渲染。这些只发生在这个阶段。
4.render(更新)
render:组件重新渲染
5.componentDidUpdate(更新) – 用来做监听器
相当于Vue中的updated,表示页面再次渲染成功。 最好不要在componentDidUpdate内调用setState 没有出口会爆栈
监听器是用来监听数据是否变化,updated
表示数据变化了,会执行updated
,也就是说数据变化了,在updated
也可以监听到了。要监听数据变化,在vue中,使用监听器比使用updated
更方便。在react中是没有监听器的概念的,在React中实现类似于Vue的监听器的功能,需要使用compoentDidUpdate
钩子函数了。
this.setState({}/fn, callback)
利用callback
也可以感知到数据变化了。推荐使用componentDidUpdate
。因为多个setState
会合并,合并后,callback
也会出问题`
三、卸载阶段:这是组件生命周期的最后阶段,组件被销毁并从 DOM 中删除。
6.componentWillUnmount(卸载)
类似于Vue中的beforeDestroy
,表示组件即将销毁。在这里我们可以:
清缓存
清除定时器
关闭长连接
销毁DOM元素
7.shouldComponentUpdate(了解)
它是控制更新阶段的开关,说白了,就是来控制是否更新,当返回true就正常更新,当返回false时就不更新。
shouldComponentUpdate
,返回true时,正常更新,返回false时,不执行更新阶段。注意细:
当执行forceUpdate时,会绕过shouldComponentUpdate方法,一定会进入到更新阶段。
shouldComponentUpdate,可以使用PureCompoentf替代
四、状态提升(就是子父组件通信)
- 一个父组件,下面有两个子组件,这两个子组件,需要通信,通常会把这个状态定义到父组件中,通过父传子,再通过子传父,实现兄弟之间的通信,状态提供到父上面,就是所谓的状态提升。
- 在React中,一般不讨论自定义属性或自定义事件,写在组件上的都叫
props
。props
的值可以是基本数据,也可以是对象
,也可以是数组
,当然也可以是函数
,也可以是jsx
。 - 如果是函数,函数分两类,一类叫
事件函数
,另一类叫渲染函数
。如果是事件函数,建议以on打头
,如果是渲染函数,建议不要使用on打头
。 - 所谓的状态提升,就是把一个状态提升到父组件中,实现
父传子
,实现子传父
。
import { Component, useState } from "react"
class Fahrenheit extends Component {
render() {
const { value, onChange } = this.props
let change = (e) => {
// 得到最新的华氏温度,需要回传给父组件
// console.log(e.target.value);
// 把华氏温度变成摄氏温度,回传给父组件
let temper = (Number(e.target.value) - 32) * 5 / 9;
onChange(Number(temper))
}
return (
<div>
<label>华氏温度:</label>
<input type="text" value={value * 9 / 5 + 32} onChange={change} />
</div>
)
}
}
function Celsius(props) {
let { value, onChange } = props;
let change = (e) => {
onChange(e.target.value);
}
return (
<div>
<label>摄氏温度:</label>
<input type="text" value={value} onChange={change} />
</div>
)
}
function DemoA() {
const [temper, setTemper] = useState(0);
return (
<div>
<Fahrenheit value={temper} onChange={val => setTemper(val)}></Fahrenheit>
<hr />
<Celsius value={temper} onChange={val => setTemper(val)}></Celsius>
</div>
)
}
export default DemoA
五、Hooks (常用)
- React的组件创建方式,一种是
类组件
,一种是纯函数组件
,并且React团队希望,组件不要变成复杂的容器,最好只是数据流的管道。开发者根据需要,组合管道即可。也就是说组件的最佳写法应该是函数,而不是类。
函数组件和类组件的区别:
(1)函数组件没有状态(state),类组件有
(2)函数组件没有生命周期,类组件有(挂载、更新、销毁)
(3)函数组件没有this,类组件有
(4)函数组件更适合做UI展示,类组件更适合做复杂的业务逻辑组件
这就注定,函数组件更适合做UI展示的功能,涉及到状态的管理与切换,我们不得不用类组件或者redux。但我们知道类组件的也是有缺点的,比如,遇到简单的页面,代码会显得很重,并且每创建一个类组件,都要去继承一个React实例;至于Redux,更不用多说,很久之前Redux的作者就说过,“能用React解决的问题就不用Redux”。
1.Hooks 含义
“Hooks”的单词意思为“钩子”。React Hooks
的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码“钩”进来。而React Hooks 就是我们所说的“钩子”。
2.Hooks的用法
useState():状态钩子
纯函数组件没有状态,useState()
用于设置和使用组件的状态属性。语法如下:
const [state, setState] = useState(initialValue);
// state:初始的状态属性,指向状态当前值,类似this.state
// setState:修改状态属性值的函数,用来更新状态,类似setState
// initialValue:状态的初始值,该值会赋给state
注意:setState
的命名为:set+State(初始状态名)
,并且采用小驼峰命名法。例如[count, setCount]
、[name, setName]
示例:使用Hooks重写计数器
const Count = () => {
const [count, setCount] = useState(0); // 将0设置为count的初始值
const addCount = () => {
let newCount = count;
setCount(newCount += 1);
}
return (
<div>
<p>{count}</p>
<button onClick={addCount}>加1</button>
</div>
)
}
用函数组件实现了一个功能完全一样的计数器,代码看起来更加的轻便简洁,没有了继承,没有了渲染逻辑,没有了生命周期等。这就是hooks
存在的意义。
useEffect():副作用钩子
useEffect()
是副作用的钩子,可以实现特定的功能,如异步请求。语法如下:
useEffect(() => {
// 回调函数,其中是要进行的异步操作代码
}, [array])
// [array]:useEffect执行的依赖,当该数组的值发生改变时,回调函数中的代码就会被指向
// 如果[array]省略,则表示不依赖,在每次渲染时回调函数都会执行
// 如果[array]是空数组,即useEffect第二项为[],表示只执行一次
示例:通过useEffect()
模拟异步加载数据(第二项省略)。
const AsyncPage = () => {
// 首先设置loading状态为true
const [loading, setLoading] = useState(true);
useEffect(() => {
// 2秒后将loading状态设置为false
setTimeout(() => {
setLoading(false);
}, 2000);
})
return (
// 判断loading是否为true,是就显示loading,不是就显示异步请求完成
loading ? <p>loading...</p> : <p>异步请求完成</p>
)
}
示例:useEffect()
依赖第二项数组变化
const AsyncPage = ({name}) => {
const [loading, setLoading] = useState(true); // 设置loading状态为true
const [person, setPerson] = useState({}); // 设置person状态为空对象
useEffect(() => {
// 首先设置loading为true,2秒后改为false,name改成传过来的参数
setLoading(true);
setTimeout(() => {
setLoading(false);
setPerson({name});
}, 2000);
}, [name]); // 表示当name修改才会执行回调函数
return (
<>
{loading ? <p>Loading...</p> : <p>{person.name}</p>}
</>
)
}
const PersonPage = () => {
// 设置初始state为空字符串
const [state, setState] = useState("");
const changeName = (name) => { // 修改name的函数
setState(name);
}
return (
<>
{/*首先将state传给name*/}
<AsyncPage name={state}/>
<button onClick={() => { // 点击按钮后将张三传给name
changeName("张三")
}}>张三
</button>
<button onClick={() => {
changeName("李四")
}}>李四
</button>
</>
)
}
useEffect和useLayoutEffect的区别
useEffect()
和useLayoutEffect()
主要的区别是调用时机不同。
useLayoutEffect()
和componentDidMount()
及componentDidUpate()
一致,在react完成DOM更新后马上同步调用代码,它会阻塞页面的渲染,而useEffect()
则会在页面渲染完后才会异步
调用。
在实际使用中如果项避免页面抖动,可以把需要操作DOM的代码放在useLayoutEffect()
中,在该函数中做DOM操作,这些DOM修改会和react做出的更改一起被一次性渲染到屏幕上,只有一次回流重绘的代价。
useCallback():记忆函数
useCallback()
为记忆函数,它可以防止因为组件重新渲染,导致方法被重新创建,起到缓存作用。语法如下:
useCallback(() => {
// 回调函数,当array改变后,该函数才会重新声明
}, [array])
// 如果[array]为空数组,那么就是第一次创建后就被缓存,如果后期array改变了,拿到的还是老的array
// 如果不传入第二个参数,每次都会重新声明一次,拿到的就是最新的array
比如说下面一段代码中,我们可以看到有很多的函数,当我们在return中修改一个状态,就会导致整个页面重新渲染,那么这些函数(handleChange1
、handleChange2
…)也会被重新创建,这样会造成性能的浪费,因此可以使用useCallback
将这些函数缓存下来,这样下一次页面重新渲染的时候,某些函数就不会重新被创建了。
const UseCallback = function () {
const handleChange1 = () => {
// 具体代码
}
const handleChange2 = () => {
// 具体代码
}
const handleChange3 = () => {
// 具体代码
}
const handleChange4 = () => {
// 具体代码
}
return (
<div>
{/*具体代码*/}
</div>
)
}
使用useCallback()
时,只需要将其写在整个函数外部即可,上面代码使用useCallback()
后效果如下,每当依赖项改变时,该函数才会被重新创建,如果依赖项不变,则不会重新创建。
const UseCallback = function () {
const handleChange1 = useCallback(
() => {
// 具体代码
}, [依赖项]
)
const handleChange2 = useCallback(
() => {
// 具体代码
}, [依赖项]
)
const handleChange3 = useCallback(
() => {
// 具体代码
}, [依赖项]
)
const handleChange4 = useCallback(
() => {
// 具体代码
}, [依赖项]
)
return (
<div>
{/*具体代码*/}
</div>
)
}
useMemo():记忆组件
useCallback()
的功能可以由useMemo()
所替代,useMemo()
也可以返回一个记忆函数,语法如下:
useMemo(() => fn, [])
// useCallback(fn, []) = useMemo(() => fn, [])
useCallback()
与useMemo()
的区别:
useCallback()不会执行第一个参数函数,而是将其返回,useMemo()会执行第一个函数并且将函数执行结果返回给你。useCallback()常用记忆时间函数,生成记忆后的时间函数传递给子组件使用,useMemo()更适合经过函数计算得到一个确定的值,比如记忆组件。
useRef():保存引用值
useRef()
等价于类组件中的React.createRef()
,语法如下:
const loginRef = useRef();
使用useRef()
创建了一个值后,就可以将其绑定到DOM节点上,给DOM节点增加一个ref
属性,将loginRef
传入,则可以通过其current
属性获取到DOM节点的值,语法如下:
<input ref={loginRef}/>
除此之外,我们都知道useState()
可以保存一个状态,那么另一个方法就是使用useRef()
,为useRef()
传入一个初始值,它可以帮助我们记住这个状态。
useContext():共享状态钩子(上下文)
useContext()
可以共享状态,作用是进行状态的分发(React16.x以后的版本支持),避免了使用Props
进行数据的传递。语法如下:
// 第一步:创建全局的Context
const AppContext = React.createContext([初始化参数])
// 第二步:通过全局的Context进行状态值的共享
<AppContext.Provider value={{ 属性名: 值 }}>
<组件1 />
<组件2 />
</AppContext>
示例:A组件和B组件共享一个状态
const Count = () => {
const AppContext = React.createContext({});
const A = () => {
const {name} = useContext(AppContext);
return (
<div>
我是A组件,我的名字是:{name}
</div>
)
}
const B = () => {
const {name} = useContext(AppContext);
return (
<div>
我是B组件,我的名字是:{name}
</div>
)
}
return (
<AppContext.Provider value={{name: "橘猫吃不胖"}}>
<A/>
<B/>
</AppContext.Provider>
)
}
useReducer():Action钩子
在使用React的过程中,如遇到状态管理,一般会用到Redux,而React本身是不提供状态管理的。而useReducer()
提供了状态管理。
useReducer()
是useState()
的替代方案。首先,关于redux我们都知道,其原理是通过用户在页面中发起action,从而通过reducer方法来改变state,从而实现页面和状态的通信。而Reducer的形式是(state, action) => newstate,返回当前的 state 以及与其配套的 dispatch 方法。。Hooks的useReducer()是这样的:
const [state, dispatch] = useReducer(reducer, initialState)
它接受reducer函数和状态的初始值作为参数,返回一个数组,其中第一项为当前的状态值,第二项为发送action的dispatch函数。
在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为可以向子组件传递 dispatch 而不是回调函数。
例如:使用useReducer()实现一个计数器
const Count = () => {
const reducer = (state, action) => {
if (action.type == "add") {
return {
...state,
count: state.count + 1
}
} else {
return state
}
}
const addCount = () => {
dispatch({
type: "add"
})
}
const [state, dispatch] = useReducer(reducer, {count: 0});
return (
<>
<p>{state.count}</p>
<button onClick={addCount}>加1</button>
</>
)
}
通过代码可以看到,使用useReducer()代替了Redux的功能,但useReducer无法提供中间件等功能,假如有这些需求,还是需要用到redux。
创建自己的Hooks
以上介绍了七种最常用的react提供给我们的默认React Hooks,有时候我们需要创建我们自己想要的Hooks,来满足更便捷的开发,就是根据业务场景对以上七种Hooks进行组装,从而得到满足自己需求的钩子。
用户自定义的Hooks:
A、命名的要求:用use开头,后跟名称(首字母大写)
B、作用:根据具体业务的需求,对Hooks中默认的钩子函数进行封装,使代码的结构更加清晰,便于使用和维护
示例:封装自己的Hooks
// 封装自己的Hooks
const usePerson = ({name}) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
setTimeout(() => {
setLoading(false)
setPerson({name})
}, 2000)
}, [name]) //第二个参数为默认
return [loading, person];
}
const AsyncPage = (name) => { // 只进行UI展示
const [loading, person] = usePerson(name)
return (
<>
{loading ? <p>Loading...</p> : <p>{person.name}</p>}
</>
)
}
const PersonPage = () => {
const [userName, setUserName] = useState('')
const changeName = (name) => {
setUserName(name);
}
return (
<>
<AsyncPage name={userName}/>
<br/>
<button onClick={() => {
changeName('张三')
}}>张三
</button>
<br/>
<button onClick={() => {
changeName('李四')
}}>李四
</button>
</>
)
}
上面代码中,将之前的例子封装成了自己的Hooks,便于共享。其中,usePerson()为自定义Hooks,它接受一个字符串,返回一个数组,数组中包括两个数据的状态,之后在使用usePerson()时,会根据传入的参数不同而返回不同的状态,然后很简便的应用于我们的页面中。
总结
React 是 Facebook 开源的,现在它是最流行的前端框架大厂必备,React 本身是一个构建 UI 的库,如果需要开发一个完整的 web 应用需要配合 react-router,redux,…
此次总结借鉴了 很多大佬文档及React官方文档,初学编程总结不完善,有误还请不吝指教!!!
React 官方中文文档