react简单入门1
一、3大框架的区别
Vue、React 和 Angular 是目前前端开发中最流行的三个框架,它们各自有着独特的特点和优势。
- Vue:学习曲线平缓,语法较为简单明了,使用html的模板语法结合指令(如
v-if
、v-for
等),快速开发中小型系统。 - React:学习曲线适中,使用JSX(TSX)语法,将 HTML 嵌入到 JavaScript 代码中,广泛应用于大型和复杂的应用。
- Angular:学习曲线陡峭,自身内置了一套强大机制,更适合构建企业级的大型应用。
参考: link.
二、react和vue的共通性
3个框架中共通的部分非常多,Angular使用较少不做说明。下边说明vue和react的部分共通内容。
1. vue的html的模板语法和react的JSX语法有很多相似性。
<!-- vue -->
<div>{{ message }}</div>
<!-- react -->
<div>{ messag }</div>
2. 都使用虚拟DOM来渲染画面。
- 虚拟DOM是一个JavaScript对象,它将DOM的状态表示为简单的JavaScript对象,并将这些对象与真实DOM的状态同步。
- 虚拟DOM的优点是它可以减少DOM的重新渲染次数,从而提高页面渲染性能。
- 当应用的数据发生变化时,Vue/React会先构建一个新的虚拟DOM树,然后Vue会将这个新的虚拟DOM树与之前的树进行对比,找出差异,最后只将需要变化的部分应用到真实的DOM上。
// react
import React from 'react';
import ReactDOM from 'react-dom';
// 定义一个简单的函数组件
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
// 渲染组件到 DOM
const rootElement = document.getElementById('root');
ReactDOM.render(<Welcome name="John" />, rootElement);
在这个例子中, 就是一个虚拟 DOM 节点,React 将它渲染成一个包含问候信息的真实 DOM 元素。当 name 属性变化时,React 会比较新旧虚拟 DOM 并只更新真实 DOM 中的文本节点,而不是整个 h1 元素。
3. 都使用生命周期概念。(钩子,hook)
生命周期:一个事物从创建到最后消亡经历的整个过程。
- vue2的生命周期:四大阶段,八大方法
阶段:初始化,挂载,更新,销毁。
方法:beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed。如下图。
vue3中生命周期和vue2区别不大,简单参照:
beforeCreate --------> setup(()=>{})
created --------> setup(()=>{})
beforeMount --------> onBeforeMount(()=>{})
mounted --------> onMounted(()=>{})
beforeUpdate --------> onBeforeUpdate(()=>{})
updated --------> onUpdated(()=>{})
beforeDestroy --------> onBeforeUnmount(()=>{})
destroyed --------> onUnmounted(()=>{})
activated --------> onActivated(()=>{})
deactivated --------> onDeactivated(()=>{})
errorCaptured --------> onErrorCaptured(()=>{})
参考: link.
- react的类组件的生命周期:只有类组件才有生命周期
阶段:挂载,更新,卸载
生命周期函数 | 触发时机 | 作用 | 函数组件对应hook |
---|---|---|---|
constructor | 创建组件时,最先执行 | 1. 初始化state 2. 创建 Ref等 | useState |
render | 每次组件渲染都会触发 | 渲染UI | 函数本身 |
componentDidMount | 组件挂载(完成DOM渲染)后 | 1. 发送网络请求 2.DOM操作 | useEffect |
componentDidUpdate | 组件更新(完成DOM渲染)后 | 可以获取到更新后的DOM内容 | useEffect |
componentWillUnmount | 组件卸载(从页面中消失) | 执行清理工作(比如:清理定时器等) | useEffect 里面返回的函数 |
参考: link.
三、react的类组件和函数组件的区别:
在React开发中,组件是构建应用程序的基本单元。
类组件:
类组件是React早期版本中使用的组件类型,类组件通常用于具有复杂状态和生命周期方法的组件。
类组件的优点是可以继承和复用,并且可以访问this关键字。
以下是一个简单的 React 类组件代码案例:
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
message: 'Hello, World!',
};
}
componentDidMount() {
// 组件挂载后执行
}
componentDidUpdate() {
// 组件更新后执行
}
componentWillUnmount() {
// 组件卸载前执行
handleClick = () => {
this.setState({ message: 'Hello, React!' });
}
render() {
return (
<div>
<h1>{this.state.message}</h1>
<button onClick={this.handleClick}>Click me</button>
</div>
);
}
}
export default App;
函数组件:
在 React 16.8 之前,函数组件被称为无状态组件,主要用于展示,不能使用状态(state)和生命周期方法。函数组件的主要优点是简洁易懂。它接收 props 作为输入,返回需要渲染的 JSX。但是在React 16.8 引入了 Hooks 特性,使得函数组件也可以有状态和生命周期,类组件几乎不再使用。
好处:
- 更简洁的代码:告别冗长的 Class 语法和繁琐的 this 绑定。
- 更好的逻辑复用:自定义 Hook 让我们能够在不同组件之间复用状态逻辑。
- 更易理解的组件:将相关的逻辑放在一起,而不是分散在不同的生命周期方法中。
下边是一个简单的函数组件代码:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hook简述:
官网概述:以 use 开头的函数被称为 Hook。Hook 比普通函数更为严格。你只能在你的组件(或其他 Hook)的 顶层 调用 Hook。如果你想在一个条件或循环中使用 useState,请提取一个新的组件并在组件内部使用它。
常用Hook:
1. useState:状态钩子。
用于为函数组件引入state状态, 并进行状态数据的读写操作
const [xxx, setXxx] = useState(initValue)
名词 | 变量函数区分 | 用法 |
---|---|---|
xxx | 变量 | 状态变量 |
setXxx | 函数 | 更新变量xxx的方法 |
initValue | 常量 | 初始值 |
- 未使用useState例子:
局部变量无法在多次渲染中持久保存。 当 React 组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
let index = 0;
function handleClick() {
index = index + 1;
}
return(
<h3>
({index + 1} 页)
</h3>
)
- 使用useState实现计数器:
import React,{ useState } from "react";
const NewCount = ()=> {
const [ count,setCount ] = useState(0)
addCount = ()=> {
let newCount = count;
setCount(newCount +=1)
}
return (
<>
<p> { count }</p>
<button onClick={ addCount }>Count++</button>
</>
)
}
export default NewCount;
2. useContext:共享状态钩子。
作用就是可以做状态的分发,避免了react逐层通过Props传递数据。也可以说是一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信。
1.创建Context容器对象:
const XxxContext = React.createContext()
2.渲染子组件时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<xxxContext.Provider value={数据}>
<子组件/>
</xxxContext.Provider>
3.后代组件读取数据:
const {} = useContext(XxxContext)
例如:A组件和B组件需要共享一个状态:
import React, { useContext } from "react";
const HookTest = ()=> {
const AppContext = React.createContext();
const A = ()=> {
const { name } = useContext(AppContext)
return (
<p>
我是A组件,我的名字是:{ name };
<span>我是A的子标签:{ name }</span>
</p>
)
}
const B= ()=> {
const { name } = useContext(AppContext);
return (
<p>我是B组件,名字是: { name }</p>
)
}
return (
<AppContext.Provider value={{ name: '张三'}}>
<A />
<B />
</AppContext.Provider>
)
}
export default HookTest;
子向父传值不仔细介绍,只展示一个例子参考资料4。
3. useEffect:副作用钩子。
类比生命周期函数包括 componentDidMount、componentDidUpdate 和 componentWillUnmount。
具体使用
useEffect(() => {
// 在此可以执行任何带副作用操作
return () => { // 在组件卸载前执行
// 在此做一些收尾工作, 比如清除定时器/取消订阅等
}
}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
依赖数组stateValue
的不同情况:
- 没有依赖数组:在组件挂载和每次重新渲染的时候都会执行。
- 空数组:在组件挂载的时候执行。
- 依赖数组:依赖项发生变化时执行。
4. useReducer:处理状态action钩子。
官网解释:对于拥有许多状态更新逻辑的组件来说,过于分散的事件处理程序可能会令人不知所措。对于这种情况,你可以将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer。
下边是使用useReducer的例子:useReducer可以理解为将状态的变更逻辑全部集成到组件函数外部,函数内部只定义用户做了什么,以及变更的数据或id。
- 使用useReducer改写前代码:
App.js
import { useState } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
export default function TaskApp() {
//tasks是个list数组
const [tasks, setTasks] = useState(initialTasks);
function handleAddTask(text) {
setTasks([
...tasks,
{
id: nextId++,
text: text,
done: false,
},
]);
}
function handleChangeTask(task) {
setTasks(
tasks.map((t) => {
if (t.id === task.id) {
return task;
} else {
return t;
}
})
);
}
function handleDeleteTask(taskId) {
setTasks(tasks.filter((t) => t.id !== taskId));
}
return (
<>
<h1>布拉格的行程安排</h1>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
let nextId = 3;
const initialTasks = [
{id: 0, text: '参观卡夫卡博物馆', done: true},
{id: 1, text: '看木偶戏', done: false},
{id: 2, text: '打卡列侬墙', done: false},
];
- 使用useReducer改写后代码:
App.js
import { useReducer } from 'react';
import AddTask from './AddTask.js';
import TaskList from './TaskList.js';
import tasksReducer from './tasksReducer.js';
export default function TaskApp() {
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
function handleAddTask(text) {
dispatch({
type: 'added',
id: nextId++,
text: text,
});
}
function handleChangeTask(task) {
dispatch({
type: 'changed',
task: task,
});
}
function handleDeleteTask(taskId) {
dispatch({
type: 'deleted',
id: taskId,
});
}
return (
<>
<h1>布拉格的行程安排</h1>
<AddTask onAddTask={handleAddTask} />
<TaskList
tasks={tasks}
onChangeTask={handleChangeTask}
onDeleteTask={handleDeleteTask}
/>
</>
);
}
let nextId = 3;
const initialTasks = [
{id: 0, text: '参观卡夫卡博物馆', done: true},
{id: 1, text: '看木偶戏', done: false},
{id: 2, text: '打卡列侬墙', done: false},
];
- tasksReducer.js
export default function tasksReducer(tasks, action) {
switch (action.type) {
case 'added': {
return [
...tasks,
{
id: action.id,
text: action.text,
done: false,
},
];
}
case 'changed': {
return tasks.map((t) => {
if (t.id === action.task.id) {
return action.task;
} else {
return t;
}
});
}
case 'deleted': {
return tasks.filter((t) => t.id !== action.id);
}
default: {
throw Error('未知 action:' + action.type);
}
}
}
5. userRef:DOM操作钩子。
主要用于获取组件中的 dom 对象,来操作 DOM。但是实际上它可以保存任何值。
const ref = useRef(initialValue)
userRef返回一个只有一个属性 current 的对象,current 初始值为 initialValue。之后可以将其设置为其他值。如果将 ref 传递给一个 JSX 节点的 ref 属性,React 将为它设置 current 属性。
改变 ref.current 属性时,React 不会重新渲染组件。
例子1:用于操作DOM节点
import { useRef } from 'react';
export default function Form() {
const inputRef = useRef(null);
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={handleClick}>
聚焦输入框
</button>
</>
);
}
例子2:使用 useRef 来保存不需要变化的值
因为 useRef 的返回值在组件的每次 render 之后都是同一个,所以它可以用来保存一些在组件整个生命周期都不需要变化的值。最常见的就是定时器的清除场景。
const App = () => {
const timer = useRef();
useEffect(() => {
timer.current = setInterval(() => {
console.log('触发了');
}, 1000);
},[]);
const clearTimer = () => {
clearInterval(timer.current);
}
return (
<>
<Button onClick={clearTimer}>停止</Button>
</>)
}
6. userMemo:数据缓存钩子。
它在每次重新渲染的时候能够缓存计算的结果。为了优化组件渲染的性能问题,有时候父组件重新渲染子组件不需要重新渲染,如果子组件的逻辑较复杂,就是无意义的大量计算,浪费资源。
useMemo 用于对计算成本较高的值进行记忆化处理,以提高性能。
在下边的示例中,useMemo 缓存了计算结果 a + b,并且只有当 a 或 b 的值发生变化时,才会重新计算结果。这样可以避免在每次组件渲染时都执行成本较高的计算操作。expensiveValue 没有变化的时候,即使父组件重新渲染了,子组件也不会重新渲染。
当需要对大量的数据进行处理和转换时,比如从服务器获取的原始数据需要进行筛选、排序、格式化等操作,这些计算都会耗费一定的时间。通过使用 useMemo 缓存处理后的数据,可以避免在每次组件渲染时都执行这些计算,提高页面性能。
import React, { useMemo } from 'react';
function ExpensiveComponent({ a, b }) {
const expensiveValue = useMemo(() => {
//计算成本较高操作
console.log('Compute expensive value');
return a + b;
}, [a, b]); // 只有在 a 或 b 发生变化时才重新计算值
return <div>{expensiveValue}</div>;
}
7. useCallBack:函数缓存钩子。
useMemo 是对数据的记忆,useCallback 是对函数的记忆。
在下边的示例中,useCallback 缓存了 handleClick 回调函数,并且只有在 count 的值发生变化时才会重新创建函数实例。这样可以避免在每次父组件渲染时都创建新的函数实例,提高性能。
import React, { useCallback, useState } from 'react';
function ParentComponent() {
const [count, setCount] = useState(0);
// 使用 useCallback 缓存回调函数,只在 count 发生变化时才重新创建函数实例
const handleClick = useCallback(() => {
console.log('Button clicked');
}, [count]);
return (
<div>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<ChildComponent onClick={handleClick} />
</div>
);
}
function ChildComponent({ onClick }) {
return <button onClick={onClick}>Click me</button>;
}
其他参考资料:
1.HTML模板语法:
1.1 变量插值使用占位符插入变量的值。例如:
<p>{{ variable }}</p>
1.2 条件语句
条件语句根据条件显示或隐藏特定的内容。例如:
{% if condition %}
<!-- 内容 -->
{% else %}
<!-- 其他内容 -->
{% endif %}
1.3 循环遍历
循环遍历用于在模板中遍历数组或字典等数据结构。例如:
{% for i in list %}
<li>{{ i }}</li>
{% endfor %}
参考: link.
2.自定义 Hook:
在React中,自定义Hook是一个函数,其名称以"use"开头,并可以在函数组件中使用。自定义Hook可以帮助我们封装和复用状态逻辑和副作用代码。
以下是一个简单的自定义Hook的例子,它用于追踪组件的挂载状态:
import { useState, useEffect, useRef } from 'react';
function useMountedState() {
const [isMounted, setIsMounted] = useState(false);
const isMountedRef = useRef(isMounted);
useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
const getIsMounted = () => isMountedRef.current;
return { isMounted: getIsMounted };
}
export default useMountedState;
使用这个自定义Hook的方法如下:
import useMountedState from './useMountedState';
function MyComponent() {
const { isMounted } = useMountedState();
// 使用isMounted函数来判断组件是否挂载
useEffect(() => {
if (isMounted()) {
// 组件处于挂载状态,执行一些操作
}
}, []);
return (
<div>
{/* 组件的内容 */}
</div>
);
}
3.react的Fiber 架构:
这是一种全新的协调引擎,旨在提高 React 应用的性能,尤其是在复杂和高频更新的场景下。可以理解为是一个更强大的虚拟DOM。
Fiber工作原理中最核心的点就是:可以中断和恢复,这个特性增强了React的并发性和响应性。
参考: link.
4.react的子向父传值:
夫组件:
import React from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const handleChildData = (data) => {
console.log('从子组件接收的数据:', data);
};
return (
<ChildComponent onDataFromChild={handleChildData} />
);
};
export default ParentComponent;
子组件:
import React from 'react';
const ChildComponent = ({ onDataFromChild }) => {
const sendDataToParent = () => {
const data = '这是子组件的数据';
onDataFromChild(data);
};
return (
<button onClick={sendDataToParent}>点击传值给父组件</button>
);
};
export default ChildComponent;
5.副作用和纯函数:
副作用是和纯函数相关的一个词,纯函数是指函数执行过程中,只有返回值,它的过程中不会让外部环境产生变化:包括但不限于修改外部变量、状态、发送网络请求、操作 DOM 等。它们只依赖于传入的参数,并且仅根据参数的值来计算返回值。
而且给定相同的输入,纯函数总是返回相同的输出,不受外部环境或状态的影响。
而副作用就是函数执行过程中,对外部环境进行的操作,在前端项目中例如数据请求等。