面试官:虽然你基础不错,但是我们的技术栈是react。我:.......???? react快速入门
自从react官方推出16.8版本出现hook以来,面向对象编程逐渐被函数式编程取代,所以本文章将全部以函数式编程,来讲解react的相关知识 最近要入职了公司用的react,不得不提前了解一下react,这里面东西可能不全,但是我后续慢慢学到会继续慢慢更新,大佬勿喷,对学完vue刚开始学react的小伙伴可能会有帮助
react基础语法
在聊hooks之前,我们先来学习一下react组件式开发的基本操作 JSX
一个简单的例子
import React from 'react';
function tset(props) {
return (
<div className="container"> //注意jsx必须只有一个跟标签 当然如果你不需要可以写成《》《/》这样的形式
<h1>Hello, World!</h1>
</div>
);
}
export default tset;
这就是一个简单的jsx模板 映入眼帘的很显然是return返回的标签。在react中,我们书写html都需要放到这个return语句中,实际上,整个jsx底层调用的是React.createElement,上面的代码其实就相当于
React.createElement("div", { className: "container" },
React.createElement("h1", null, "Hello, World!")
很显然,这样的写法显得十分麻烦,于是推出了jsx来做React.createElement语法糖。在jsx return里面,你可以几乎使用完全的html书写习惯,但是注意只是几乎,特别的,在html标签中class将不在使用而转为className,要始终记得,在return语句中,你书写的不是html,而是类似与js书写的标签,class转换为className,使用-连接的属性要变成大写
了解简单的html书写之后,我看一下样式如何添加上去
以上面的container为例,我们无法直接在jsx里面书写类似这样子
.container{
width:100%;
height:100%;
}
如果需要上样式的话,我们有两种常用的手段
1 我们使用<div style={container}> 使用内敛样式的形式,虽然jsx我们无法直接书写css代码,但是我们可以通过变量的形式保存css字符串,然后当作内联样式嵌入html
例如这样
const container= {
width: "100px",
height: "100px",
background: "red",
}; //注意用逗号哦,类似于对象的写法,时刻谨记,jsx中书写的是类似于css或者html的js
第二种办法
我们将css抽离出来,单独形成css文件,随后引入达到样式的书写
import "./css/App.css";,
这样我们直接写<div className="container"> 使用className的形式上样式即可
.container {
width: "100px";
height: "100px";
background: "red";
}
接下来我们了解一下在jsx中,如何使用类似于v-for 和v-if进行渲染
在vue里面,我们很容易就写出 这样的形式,之后vue就会帮我们循环渲染到模板中,但是在react却一样
<ul>
<li v-for="item in 20" :key="item.id"></li>
</ul>
以下面例子为例
let arr= [
{ id: "01", content: "js", age: 13 },
{ id: "02", content: "html", age: 15 },
{ id: "03", content: "css", age: 20 },
],
假设我们需要以ul li的形式渲染到页面上,我们肯定无法直接写成这样,没有对应的指令让我们这样书写
<ul>
<li ???="item in arr" :key="item.id"></li>
</ul>
正确的做法应该是
const show=arr.map((e)=>{
return (
<ul key={e.id}> //注意,身为父元素的ul必须加上唯一的id,否则控制台会报错
<li>{e.content}{e.age}</li>
</ul>
)
})
然后在jsx中,这样就可以实现循环渲染
function tset(props) {
return (
<div className="container">
{show}
</div>
);
}
我们不妨打印一下看看返回的是什么东西,不难看出,返回的结果个数组,数组里面是经过react.element构建来的
了解上述之后,我们应该不难想出,条件渲染是如何的
let show2 = show.length > 5 ? show2 : null;我们重新定义一个变量,来进行判断,当然这里我只采用了简单的三目,写成函数返回也是可以的
之后我们要展示的就不是show了而是show2
<div className="container">
{show2}
</div>
hook与事件
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks 的出现,首先能解决如下的一些问题:
- 告别令人疑惑的生命周期
- 告别类组件中烦人的 this
- 在类组件中,会存在 this 的指向问题,例如在事件处理函数中,不能直接通过 this 获取组件实例,需要修改 this 指向
- 告别繁重的类组件,回归前端程序员更加熟悉的函数
另外,Hooks 的出现,还有更加重要的一个信号,那就是整个 React 思想上面的转变,从“面向对象”的思想开始转为“函数式编程”的思想。这是编程范式上面的转变。
编程范式:
- 命令式编程:告诉计算机怎么做(How),我们需要给计算机指明每一个步骤
- 面向过程
- 面向对象
- 声明式编程:告诉计算机我要什么(What)
- 函数式编程
- DSL(领域专用语言,HTML、CSS、SQL)
声明式编程并不是新的产物,它是和命令式编程同期出现的。但是,早期更加流行命令式编程。不过随着近几年整个项目工程越来越复杂,以前的命令式编程就有点力不从心,所以现在慢慢开始流行声明式编程。
因此当你学习 Hooks 的时候,会发现突然多了一些以前不熟悉的概念,例如:纯函数、副作用、柯里化、高阶函数等概念。
当然,你可能好奇“面向对象”和“函数式编程”有什么区别,这里推荐一篇文章:
www.imaginarycloud.com/blog/functi…
Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:
- 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
- 只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。
- 你可以使用
useEffect
来模拟componentDidMount
、componentDidUpdate
和componentWillUnmount
:useEffect
的回调函数会在组件挂载(相当于componentDidMount
)和更新(相当于componentDidUpdate
)后执行。- 如果
useEffect
的回调函数返回了一个清理函数,该函数会在组件卸载(相当于componentWillUnmount
)时执行。
第一个hook
在react16.8以后中,hook的出现大大提高了开发的效率,在react中也存者类似于vue的响应式,虚拟dom,单项数据流等等
在这里我们只简单介绍两个hook,后续在做补充
1 useState 详细看官方文档 https://react.docschina.org/reference/react/useState
useState 是一个 React Hook,它允许你向组件添加一个 状态变量。
·为组件添加状态
·根据先前的 state 更新 state
·更新状态中的对象和数组
·避免重复创建初始状态
·使用 key 重置状态
·存储前一次渲染的信息
基本用法
import { useState } from 'react'; 从react上引入 是不是有点类似于 import { ref } from 'vue'呢
而useState用法如下,useState传入你要定义的数据,然后会返回一个包括数据的变量和针对对应数据操作的方法函数
习惯上我们使用【age,setAge】这样的格式书写 age为你定义的数据28,setAge是针对当前数据进行操作的函数
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
不妨看一下结构出来的两个是什么
可以看出,拿到的是简单的数据,还有一个复杂的函数,我们不必关系他到底什么是,我们只需要会使用即可
接下来,我们做一个简单的例子,在那之前,我们简单了解一下react绑定事件的方式
react的事件绑定并不像vue一样采用@语法糖,而是有些类似于原生html写事件一样(内敛)
原生html我们可以 <button onClick="hancleClick(e)">点我修改age </button>
在react中,写法也是类似
<button onClick={(e) => changeAge(e)}>点我修改age </button>
注意注意,不难这样写<button onClick={changeAge(e)}>点我修改age </button> 如果你想要传递参数的话,不可以这样写的,需要写成上述的形式。当然,如果你没有参数需要传递<button onClick={changeAge}>可以写成这样
ok,我们尝试一下
import React, { useState } from "react";
function Tset(props) {
const [age, setAge] = useState(28);
function changeAge(val) {
setAge((e) => {
return e + val;
});
}
return (
<div className="container">
<h1>{age}</h1>
<button onClick={() => changeAge(1)}>点我+1</button>
</div>
);
}
export default Tset;
在上述例子中,我需要指出的是
{age}就是类似于vue的{{age}},将数据展示到页面上
然后changeAge接收一个参数val(1),在里面使用setAge对age进行操作,注意,不能直接对age进行操作
类似于
function changeAge(val) {
再次注意,请不要对数据直接进行操作,如果需要操作数据请使用setAge方法,在里面进行操作
age=age+val //控制台报错 Assignment to constant variable.
setAge((e)=>{
//return e++ //不能直接对数据进行操作
return e + 1; 可以写这样
})
如果你想将age变为一个固定的值,你也许可以这样
setAge(1) //这样的写法类似于 setAge(()=>1) 会将ag变为固定值
}
整体来说
setAge的返回值会替换掉原来的Age的值,所以这就是为什么请不要对数据进行直接操作的原因
在这里我需要指出,在hook之前,我们使用this.state = {
xxx : xxx 来进行赋值
}
使用this.setState({
xxx: 新值 进行修改
})
请注意,如果使用这样的写法,请永远记住,setSate是异步操作。
我们不妨打印一下看看这个setAge((e)=>{consle.log(e)})是什么东西
不难看到,数据已经变化了,但是打印出来的e却是上一个值
所以我们需要记住,setAge中传来的参数是上一个值,而非最新值
第二个Hook
副作用的概念
- 纯函数:一个确切的参数在你的函数中运行后,一定能得到一个确切的值,例如下面的例子:
function test(x){
return x * 2;
}
x => 2 ===> 4
x => 3 ===> 6
- 如果一个函数中,存在副作用,那么我们就称该函数不是一个纯函数,所谓副作用,就是指函数的结果是不可控,不可预期。
- 常见的副作用有发送网络请求、添加一些监听的注册和取消注册,手动修改 DOM,以前我们是将这些副作用写在生命周期钩子函数里面,现在就可以书写在 useEffect 这个 Hook 里面
第二个hook是useEffect,用来处理副作用
简单介绍一下useEffect,useEffect传入两个参数,第一个参数是要执行的函数,第二个参数是依赖项
怎么说呢
如果不传入第二个依赖项
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
document.title = `你点击了${count}次`;
console.log(`你点击了${count}次`);
});
这样写的话,就类似于vue中的update生命周期,每当组件进行更新或者页面发生变化的时候就会调用一次
如果
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
document.title = `你点击了${count}次`;
console.log(`你点击了${count}次`);
},[]);
传入一个空的依赖项,那么这个就只会执行一次,有没有类似于 vue mounted的生命周期呢,
所以这种情况就可以用来发请求获取数据,如果没有传入依赖项,那么就会不断进行发送请求死循环了
如果
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
document.title = `你点击了${count}次`;
console.log(`你点击了${count}次`);
},[count]);
如果传入了对应的依赖项,那么就有点类似于 vue的watch(()=>??,()=>{},{imider...首次监视})了,根据依赖项的变化进行执行,如果依赖项变化了就执行函数。
import { useState, useEffect } from "react";
function App() {
let [count, setCount] = useState(0);
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
document.title = `你点击了${count}次`;
console.log(`你点击了${count}次`);
});
function clickhandle() {
setCount(++count);
}
return (
<div>
<div>你点击了{count}次</div>
<button onClick={clickhandle}>+1</button>
</div>
);
}
export default App;
另外,函数还可以有返回值,也是一个函数,用来处理函数产生的副作用,我们叫清理函数
看这样一种情况,在这里我们在副作用函数里写了一个计时器,我们很容易想到,不断的点击调用副作用函数,计时器也会发生高频触发的情况,显然这不是我们想要的
import { useState, useEffect } from "react";
function App() {
let [count, setCount] = useState(0);
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
console.log(`你点击了${count}次`);
setInterval(() => {
console.log("点击了");
}, 1000);
}, [count]);
function clickhandle() {
setCount(++count);
}
return (
<div>
<div>你点击了{count}次</div>
<button onClick={clickhandle}>+1</button>
</div>
);
}
export default App;
想要解决这种情况
import { useState, useEffect } from "react";
function App() {
let [count, setCount] = useState(0);
useEffect(() => {
// 书写你要执行的副作用,会在组件渲染完成后执行
// //初次渲染执行一次
// //如果希望副作用只执行一次,传入第二个参数为空数组为依赖数组
console.log(`你点击了${count}次`);
let time = setInterval(() => {
console.log("点击了");
console.log("副作用函数执行");
}, 1000);
return () => {
~~~~~~~~可以看到,这里副作用函数返回了一个新的函数,我们称之为清理函数,用来处理因为副作用函数产生的副作用的
clearInterval(time);
console.log("清理函数执行");
//初次渲染不会执行
// //清理函数,清理函数会在渲染完成后,副作用函数执行前执行
};
// //在这个数组中写入了谁依赖项,谁变化才会引起副作用函数与清理函数
// //如果想只执行一次的话,写入空数组即可,例如发起网路请求的时候
}, [count]);
function clickhandle() {
setCount(++count);
}
return (
<div>
<div>你点击了{count}次</div>
<button onClick={clickhandle}>+1</button>
</div>
);
}
export default App;
自定义hook
除了使用官方内置的 Hook,我们还可以自定义 Hook,自定义 Hook 的本质其实就是函数,但是和普通函数还是有一些区别,主要体现在以下两个点:
- 自定义 Hook 能够调用诸如 useState、useRef 等,普通函数则不能。由此可以通过内置的 Hooks 获得 Fiber 的访问方式,可以实现在组件级别存储数据的方案等。
- 自定义 Hooks 需要以 use 开头,普通函数则没有这个限制。使用 use 开头并不是一个语法或者一个强制性的方案,更像是一个约定。
App.jsx
import { useState } from 'react';
import useMyBook from "./useMyBook"
function App() {
const {bookName, setBookName} = useMyBook();
const [value, setValue] = useState("");
function changeHandle(e){
setValue(e.target.value);
}
function clickHandle(){
setBookName(value);
}
return (
<div>
<div>{bookName}</div>
<input type="text" value={value} onChange={changeHandle}/>
<button onClick={clickHandle}>确定</button>
</div>
)
}
export default App;
useMyBook
import { useState } from "react";
function useMyBook(){
const [bookName, setBookName] = useState("React 学习");
return {
bookName, setBookName
}
}
export default useMyBook;
生命周期
React Hooks 是 React 16.8 引入的新特性,它们允许你在不编写类组件的情况下使用状态和其他 React 特性。Hooks 并不直接提供传统类组件中的生命周期方法(如 componentDidMount
、componentDidUpdate
、componentWillUnmount
等),但你可以使用一些特定的 Hooks 来模拟这些生命周期行为。
以下是几个关键的 React Hooks,以及它们如何与生命周期方法相对应:
useState
:允许你在函数组件中添加状态。useEffect
:这是一个强大的 Hook,可以用来模拟大部分生命周期方法的行为。它可以在组件渲染后执行副作用,并且在组件卸载时清理这些副作用。useLayoutEffect
:与useEffect
类似,但它会在所有的 DOM 变更之后同步执行副作用。如果你需要读取 DOM 布局并同步触发重渲染,使用它。useCallback
:返回一个记忆化的回调函数,只有在依赖项改变时才会更新。useMemo
:返回一个记忆化的值,只有在依赖项改变时才会重新计算。useRef
:返回一个可变的 ref 对象,其.current
属性被初始化为传入的参数,类似于类组件中的createRef
。useImperativeHandle
:用于自定义使用ref
时公开给父组件的实例值。useContext
:用于访问 React Context。
虽然这些 Hooks 不能一对一地映射到生命周期方法,但你可以使用它们来实现类似的功能。例如,你可以使用 useEffect
来模拟 componentDidMount
、componentDidUpdate
和 componentWillUnmount
:
useEffect
的回调函数会在组件挂载(相当于componentDidMount
)和更新(相当于componentDidUpdate
)后执行。- 如果
useEffect
的回调函数返回了一个清理函数,该函数会在组件卸载(相当于componentWillUnmount
)时执行。
受控组件与非受控组件
了解之后我们来了解一下受控组件与非受控组件
受控组件
无论是学习 Vue,还是 React,最重要的是要转换思想,这一步非常重要,往往也比较困难。
在以前 jQuery 时代,开发人员需要获取到 DOM 节点,然后进行操作。而在现代前端开发中,采用的是 MVVM 的模式,将视图和视图模型进行绑定,视图模型的改变,会自然的带来视图的改变。开发人员需要专注在视图模型上面。
因此,这里所谓的受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。
例子1
注意,在这里我=我们使用了inp标签,如果想要拿到inp标签的内容,我们仍然还是需要使用传统的dom.val进行拿到数据
import React, { useState } from "react";
function Tset(props) {
let [state, setState] = useState(""); //使用useState进行数据定义
注意,如果不对state值进行重新赋值的话,inp输入框里面是不会有你输入的东西的,这就是受控组件
function handleChange(e) { //inp的change方法
setState(() => {
return e.target.value;
});
如果全部注释掉的话,界面input输入框中无论你输入什么,都不会有任何显示,inp输入框的值收到state的显示,state有值,则输入框有值,state如果没有值,则输入框也没有值,且无法输入值
因此,这里所谓的受控组件,本质上其实就是将表单中的控件和视图模型(状态)进行绑定,之后都是针对状态进行操作。
// setState(() => {
// return e.target.value;
//});
}
function clickHandle() {
// 提交整个表单
console.log(`你要提交的内容为:${state}`);
}
return (
<div>
<input type="text"
value={state} //绑定state
onChange={(e) => handleChange(e)} />
<button onClick={clickHandle}>提交</button>
</div>
);
}
export default Tset;
例子2对用户输入的内容进行限制
import React, { useState } from "react";
function Tset(props) {
const [xiaoxie, setXs] = useState();
const [shuzi, setSz] = useState();
function changes(e) {
let name = e.target.name; //判断是对第几个输入框进行输入
switch (name) {
case "one":
//根据不同的输入框执行不同的操作
setXs(() => {
return e.target.value.toUpperCase(); //将用书输入的小写转化为大写,并且覆盖掉原来的值
});
break;
case "two":
//根据不同的输入框执行不同的操作
if (Number(e.target.value)) {
setSz(e.target.value);
break;
} else {
alert("请输入数字");
setSz((e) => e);
break;
}
}
}
return (
<div>
<input
type="text"
name="one"
onChange={changes}
value={xiaoxie}
placeholder="请输入小写"
></input>
<input
type="text"
name="two"
onChange={changes}
value={shuzi}
placeholder="请输入数字"
></input>
</div>
);
}
export default Tset;
例子3下拉列表
这里要注意的是,针对下拉列表,与input同理,受控组件收到设置的值的约束,如果需要改变值需要使用setValue方法
当然你可以直接写道onChange里面, onChange={(e) => setValue(e.target.value)}就像这样,直接将当前选择的值覆盖掉原来的
import React, { useState } from "react";
function Tset(props) {
const [value, setValue] = useState("css");
return (
<div>
<select
onChange={(e) => setValue(e.target.value)}
value={value} //绑定value
key="sss"
>
<option value="html">html</option>
<option value="css">css</option>
<option value="js">js</option>
</select>
</div>
);
}
export default Tset;
例子4多选框
针对多选框
import React, { useState } from "react";
function Tset(props) {
let [checks, setChecks] = useState([
{ content: "js", checked: true },
{ content: "html", checked: false },
{ content: "css", checked: true },
]);
function handelChange(index) {
setChecks((e) => {
!!!! 再次注意,请不要直接对数据进行操作,如果有必要,请先将数据生成一份保存下来,如下
const newChecks = [...e];
随后针对新的数据进行操作,操作完之后在完全覆盖掉原来的数据
newChecks[index].checked = !newChecks[index].checked;
return newChecks;
});
}
return (
<div>
//当然,如果你这样写不习惯,你也可以选择使用变量然后进行map操作将生成的jsx保存到变量然后放到此处
//这里使用了index当作对应的id,实际情况下,尽量还是给对应的数值设置唯一的id,避免特殊情况
{checks.map((e, index) => {
return (
<div key={index}>
<span>{e.content}</span>
<input
type="checkbox"
value={e.content}
checked={e.checked}
onChange={() => handelChange(index)}
></input>
</div>
);
})}
</div>
);
}
export default Tset;
非受控组件
大多数情况下,在 React 中推荐使用受控组件来对表单进行操作,这样能够对表单控件的数据进行一个统一管理。
但是在某些特殊情况下,需要使用以前传统的 DOM 方案进行处理,此时替代的方案就是非受控组件
首先介绍下类似于vue中的ref绑定dom的方法
在react中,使用 const fileRef = React.createRef();创建一个ref,类似于vue的 ref,仅作用于绑定,不少响应式!!
例子1
const fileRef = React.createRef();
<input ref={fileRef} type="file"></input>
通过fileRef可以拿到input的实例
非受控组件默认值
const inputCon = React.createRef();
<div>
{/* 一旦你用了 value,React 会认为你这是一个受控组件 */}
{/* 如果想要给默认值,使用 defaultValue */}
<input type="text" ref={inputCon} defaultValue="123"/>
<button onClick={this.clickHandle}>获取用户输入的内容</button>
</div>
文件上传
import React from "react";
// 类组件
const uploadRef = React.createRef();
function clickHandle() {
console.log(uploadRef.current.files);
}
return (
<div>
<input type="file" ref={uploadRef}/>
<button onClick={clickHandle}>获取用户输入的内容</button>
</div>
)
export default App;
组件通信
最常用的莫过于props了,在初始模板我们很容易看到function中存在着props
import React from 'react';
function App(props) {
return (
<div>
</div>
);
}
export default App;
这个props就是为了接收参数而来的
相比vue的组件通讯,react似乎要舒服一点
父组件:
import React from "react";
import Child from "../component/child";
import { useState } from "react";
function App(props) {
const [dataList, setDataList] = useState([
{
id: "01",
name: "dz1",
age: 20,
},
{
id: "02",
name: "dz2",
age: 20,
},
{
id: "03",
name: "dz3",
age: 20,
},
]);
function AddDataList(val) {
setDataList((e) => {
~~~再次注意,请不要对元数据进行操作,如果有必要,请先保存一份然后操作后覆盖掉元数据
let neList = [...e];
neList.push(val);
return neList;
});
}
return (
<div>
//直接在子组件标签上写对应要传递的属性即可
<Child dataList={dataList} AddDataList={AddDataList}></Child>
</div>
);
}
export default App;
子组件:
import React from "react";
function child(props) {
let list = props.dataList;
//子组件通过props进行接收
let show = list.map((e) => {
//渲染数据展示出来
return (
<ul key={e.id}>
<li>
{e.name} {e.age}
</li>
</ul>
);
});
function add() {
//仿造子传夫情况,子组件获取一部分数据,传递给父组件,父组件调用自己的方法拿到子组件的数据
let obj = {
id: Math.random(),
name: `${Math.floor(Math.random() * 10)}asd`,
age: Math.floor(Math.random()) * 10,
};
props.AddDataList(obj);
}
console.log(props);
return (
<div>
{show}
<button onClick={add}>点我增加一条数据</button>
</div>
);
}
export default child;
我们还可以堆组件传递的props的数据进行限制类似于vue中的props的限制,不过在react中,我们需要安装第三方库
npm install --save prop-types
有关 props 能够验证的类型,官网实际上已经全部罗列出来了。
对应地址:https://zh-hans.reactjs.org/docs/typechecking-with-proptypes.html#proptypes
以上面的例子为例
如果我需要对父组件传递的参数进行验证,那么我们应该有如下
import PropTypes from "prop-types"; //
let list = props.dataList;
child.propTypes = { //注意这里的书写格式,函数名+propTypes,注意小写
dataList: PropTypes.array, //对数据类型进行限制
AddDataList: PropTypes.func,
};
假设我们父组件传递的 AddDataList不再是一个函数,那么控制台会报错如下
自定义验证
自定义验证,自定义验证我们需要以函数的形式进行验证,验证失败则返回一个error
父:
const [name, setName] = useState("name");
<Child dataList={dataList} AddDataList={AddDataList} name={name}></Child>
子:
child.propTypes = {
dataList: PropTypes.array,
AddDataList: PropTypes.func,
/**
*
* @param {*} props 整体的props对象
* @param {*} propName 当前验证的props对象
* @param {*} componentName 组件名
*/
name: function (props, propName, componentName) {
console.log("props", props, "propName", propName, "com", componentName);
let reg = /[s\b]/g;
if (!reg.test(props[propName])) {
//验证错误
return new Error("不是以s结尾的");
}
},
};
高阶组件
高阶组件,简称HOC,是React中一种逻辑的复用技巧,类似于公共逻辑的抽离。
····高阶组件并非组件,而是增强组件功能的一个函数
官方对于高阶组件的定义:
高阶组件是参数为组件,返回值为新组件的函数。
const EnhancedComponent = higherOrderComponent(WrappedComponent);
····高阶组件是对多个组件公共逻辑的横向抽离
高阶组件作为一个函数,接收你传入的组件,然后又返回一个新组件给你,那你猜都猜得到在高阶组件的内部肯定是对你原有的组件做了一些增强操作,然后为你返回的是增强后的组件。
那什么又叫做对组件公共逻辑进行横向抽离呢?看下图:
假设我们这里有三个组件,每个组件有一部分公共逻辑,一部分该组件自身的业务逻辑,那么很明显每个组件都书写一遍这样的公共逻辑是不划算的。
作为一个程序员,我们自然而然想到的就是将这部分公共逻辑提取出来。
早期的 React 采用的是 mixins 来解决这种横切关注点相关的问题。Mixins 的原理可以简单理解为将一个 mixin 对象上的方法增加到组件上
之后 React 推出了高阶组件的抽离方式,如下图所示:
在高阶组件中,接收一个组件作为参数,然后在高阶组件中会返回一个新组件,新组件中会将公共逻辑附加上去,传入的组件一般作为新组件的视图。
来个例子
首先我们看这样一段代码
父组件:
import Child2 from "../component/Child2";
import Child3 from "../component/Child3";
const [name, setName] = useState("name");
const [age, setAge] = useState(18);
<Child2 name={name}></Child2>
<Child3 age={age}></Child3>
Child2组件:
import React, { useEffect } from "react";
function Child2(props) {
useEffect(() => {
console.log("child2创建");
return function () {
console.log("child2销毁");
};
},[]);
return (
<div>
<h1>{props.name}</h1>
</div>
);
}
export default Child2;
Child3 参考Child2
很显然,每次渲染child2和3的时候都会触发一次组件的创建与销毁
但是很显然的一点是,我们发现child2和3的组件里都会有很长的重复代码,显得很荣誉,这时高阶组件就发挥作用了
高阶组件
再次提醒,高阶组件不是组件,而是一个接收组件的函数
创立HOC文件夹
建立jsx,一般我们对高阶组件的命名都使用 withXxxx
这里我创建了withChild
withChild:
import { useEffect } from "react";
export default function (Com) {
return function newCom(props) {
//抽离了公共逻辑
useEffect(() => {
console.log(`${Com.name}创建了`);
return function () {
console.log("====================================");
console.log(`${Com.name}销毁`);
console.log("====================================");
};
}, []);
//一般来讲,传入的组件会作为返回的视图
return <Com {...props}></Com>;
};
}
父组件:
import Child2 from "../component/Child2";
import Child3 from "../component/Child3";
import withChild from "../HOC/withChild";
const NewCom2 = withChild(Child2); //拿到高阶组件返回的组件
const NewCom3 = withChild(Child3);
const [name, setName] = useState("name");
const [age, setAge] = useState(18);
const [statu, setStatu] = useState(true);//定义状态控制
<button
onClick={() => {
setStatu(!statu);
}}
>
点我切换statu的状态
</button>
{statu ? <NewCom2 name={name}></NewCom2> : <NewCom3 age={age}></NewCom3>}
我们不妨看一下,在高阶组件里面接收的Com,props,以及在父组件接收的返回的组件,都是什么
不难看出, NewCom2和NewCom3返回的是是一个新的组件而, props就是父组件传递过来的值,但是我们要知道,在父组件中我们传递的参数传递到了高阶组件里面,并没有直接传递到子组件,所以在高阶函数里我们还需要在返回值里吧数据再次返回给子组件。 return <Com {...props}> 注意看这种写法,我们将父组件传递给高阶函数的参数以结构的方式再次传递给子组件。 com则是我们传递过去的组件
整体就像 父组件--->传递参数&&子组件---->HOCHOC---->接收参数,添加公共逻辑,解构参数传递给子组件,并且返回添加公共逻辑后的子组件
值得一提的是,高阶组件直接可以进行嵌套
比如
在上述例子上,我们重新进行封装
withTimer:
import { useEffect } from "react";
export default function (Com) {
return function newCom(props) {
//抽离了公共逻辑
useEffect(() => {
let tim = setInterval(() => {
console.log(1);
}, 1000);
return function () {
clearInterval(tim);
};
}, []);
//一般来讲,传入的组件会作为返回的视图
return <Com {...props}></Com>;
};
}
import { useEffect } from "react";
import withTimer from "./withTimer";
export default function (Com) {
return function newCom(props) {
//抽离了公共逻辑
useEffect(() => {
console.log(`${Com.name}创建了`);
return function () {
console.log(`${Com.name}销毁`);
};
}, []);
//一般来讲,传入的组件会作为返回的视图
let NewCom = withTimer(Com);
return <NewCom {...props}></NewCom>;
};
}
在withChild组件里又套用了withTimer,使得两个公共逻辑进行嵌套
react-router
index.js
import { BrowserRouter,HashRouter } from "react-router-dom"; //history 模式,HashRouter 哈希模式
<BrowserRouter>
<App />
</BrowserRouter>
使用模式的时候需要讲app外侧进行包裹,以开启指定的模式
路由配置 一
import { NavLink,Routes,Route } from "react-router-dom"; //导航按钮,导航配置,导航设置
<NavLink to="/home" className="navigation"> //navlink就是router-link,做导航按钮使用
主页
</NavLink>
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/add" element={<Add />} />
<Route path="/about" element={<About />} />
<Route path="/detail/:id" element={<Detail />} />
<Route path="/edit/:id" element={<Add />} />
<Route path="/" element={<Navigate replace to="/home" />} />
</Routes>
方法二
新建route文件,新建route.js
import React from "react";
import { Navigate, useRoutes } from "react-router-dom"; //navigate 做重定向使用 useRoutes用来写路由配置
import Add from "../components/Add";
import Home from "../components/Home";
import About from "../components/About";
import Detail from "../components/Detail";
import Emil from "../components/Emil";
function Route(props) {
return useRoutes([
{
path: "/home",
element: <Home />,
},
{
path: "/add",
element: <Add />,
},
{
path: "/about",
element: <About />,
children: [
{
path: "emil",
element: <Emil></Emil>,
},
],
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/edit/:id",
element: <Add />,
},
{
path: "/",
element: <Navigate replace to="/home"></Navigate>,
},
]);
}
export default Route;
在app.jsx里
import RouteConfig from "./route/route"; 引入刚刚配置的route文件
使用
<RouteConfig></RouteConfig> 表示路由显示的位置
子路由展示与编程式与导航参数导航
import { NavLink, useNavigate, Outlet } from "react-router-dom";
NavLink //导航组件 useNavigate返回一个路由器,通过路由器控制编程路由 outlet 子路由展示区域
let navgite = useNavigate();
function toEmli() {
navgite("/about/emil"); //进行路由跳转
}
<h1 className="page-header">使用说明</h1>
<p>学习reatc-router</p>
<span onClick={toEmli}>点击进入emil</span>
<NavLink to="/about/emil">进入emli</NavLink>
{/* 用于在父组件子路由的展示 */}
<Outlet></Outlet>
路由参数与获取路由参数
使用编程时导航进行路由跳转,同时增加路由参数
navigate("/home", {
state: {
alert: "学生删除成功",
type: "info",
},
});
import { useLocation } from "react-router-dom";
//useLocation 返回路由信息
let location = useLocation();
location.state //获取路由传来的参数
//导航组件传递参数
<NavLink to={`/detail/${e.id}`}>详细</NavLink>
{ 注意在路由配置里面的写法
path: "/detail/:id",
element: <Detail />,
},
react-redux+react-toolkit
安装
npm i @reduxjs/toolkit react-redux
index.js 配置
import { Provider } from "react-redux"; 引入provider包裹app,并且将store传递下去
import store from "./store/store";
root.render(
<Provider store={store}>
<App />
</Provider>
);
store.js
import { configureStore } from "@reduxjs/toolkit"; //引入创建仓库
import toDoList from "./todoListSlice"; //引入仓库切片
export default configureStore({
//传入配置对象
reducer: {
todo: toDoList,
},
});
todoList.js
import { createSlice ,createAsyncThunk} from "@reduxjs/toolkit"; //创建切片
//创建切片
//createAsyncThunk 写异步操作发请求的
export const toDoList = createSlice({
name: "todoList", //类似仓库名,命名空间
initialState: {
//书写仓库数据
list: {
name: "asd",
age: 20,
},
},
//reducers 修改仓库里面的数据方法,类似于action
reducers: {
/**
*
* @param {*} state 仓库数据
* @param {*} val 传递过来的数据
*/
add: (state, { payload }) => {
//允许直接修改数据
state.list[0].age = state.list[0].age + payload;
console.log("state", state, "val", payload);
},
changes2: (state, { payload }) => {
console.log(payload);
state.list.push(payload);
},
},
});
console.log(toDoList.reducer, toDoList.actions);
// 切片上的reducer是一个函数,会提供仓库的数据,仓库的方法在actions中
export default toDoList.reducer;
app.js 使用仓库数据
import { useSelector } from "react-redux";
function App() {
let { list } = useSelector((state) => {
console.log("state", state); //拿到仓库的数据
return state.todo;
});
const list2 = list.map((e) => {
return (
<ul key={e.name}>
<li>{e.name}</li>
<li>{e.age}</li>
</ul>
);
});
使用action方法
import { useDispatch } from "react-redux";
import { add, changes2 } from "./store/todoListSlice";
let dispatch = useDispatch(); 调用useDispatch返回一个dispatch方法,使用这个方法调用函数
function changeAge(val) {
dispatch(add(val));
let obj = { name: Math.random(), age: val * 10 };
dispatch(changes2(obj));
}
例子
简易版学生管理系统
转载自:https://juejin.cn/post/7362203195051868223