根据Vue对比来深入学习React 下 props 组件传值 插槽 样式操作 hooks 高阶组件 性能优化
插槽本质上讲就是子组件的html内容需要父组件传入,在jsx的加持下,我可以把html像普通的字符串,数字一样传递,所以插槽只需要直接作为props传入就行。所以React最大的一个性能问题就是-React的某个组件的更新会连带着,他的子组件一起更新。React的更新是调用方法时触发的,并没有依赖收集的过程所以他会更新整个组件树也就是会把子组件一起更新即使更新的数据和子组件没有任何关系。组件是既包
文章目录
函数组件的特点
- 函数组件没有生命周期
- 函数组件没有this
- 函数组件通过hook来完成各种操作
- 函数组件本身的函数体相当于render函数
- props在函数的第一个参数接受
props
props是react中的核心
在react中,一切写在组件上的属性和子节点都被规划为了props。
所以props是react很多功能的根本。父子传值,插槽全都是基于props,不像Vue有事件监听,emit,专门的插槽这一类东西
//也可以给默认值
function Avatar({ person, size= 100 }) {
return (
<img
className="avatar"
src={getImageUrl(person)}
alt={person.name}
width={size}
height={size}
/>
);
}
export default function Profile() {
return (
<div>
<Avatar
size={100}
person={{
name: 'Katsuko Saruhashi',
imageId: 'YfeOqp2'
}}
/>
</div>
);
}
组件间的传值
父传子看上例
子传父
子组件可以通过回调函数将数据传递回父组件。没有Vue中的emit和expose方法
// 子组件
const ChildComponent = ({ onSendData }) => {
return <button onClick={() => onSendData("Hello from Child!")}>Send Data</button>;
};
// 父组件
const ParentComponent = () => {
const handleData = (data) => {
console.log(data);
};
return <ChildComponent onSendData={handleData} />;
};
兄弟组件传值
与Vue相同 都是通过父组件作为中介来传递数据。
// 第一个兄弟组件
const BrotherOne = ({ onSendData }) => {
return <button onClick={() => onSendData("Data from Brother One")}>Send Data</button>;
};
// 第二个兄弟组件
const BrotherTwo = ({ data }) => {
return <h1>{data}</h1>;
};
// 父组件
const ParentComponent = () => {
const [data, setData] = React.useState("");
const handleDataChange = (newData) => {
setData(newData);
};
return (
<div>
<BrotherOne onSendData={handleDataChange} />
<BrotherTwo data={data} />
</div>
);
};
祖先组件传值
import React, { createContext, useContext, useState } from 'react';
JJ// 创建 Context
const MyContext = createContext();
// 提供者组件
const MyProvider = ({ children }) => {
const [value, setValue] = useState("Hello from Context!");
return (
<MyContext.Provider value={{ value, setValue }}>
J{children}
</MyContext.Provider>
);
};
// 使用 Context 的子组件
const ChildComponent = () => {
const { value } = useContext(MyContext);
return <h1>{value}</h1>;
};
// 父组件
const ParentComponent = () => {
return (
<MyProvider>
<ChildComponent />
</MyProvider>
);
};
插槽
在 React 中,虽然没有 Vue 的插槽(slot)具名插槽 作用域插槽,但是可以实现
插槽本质上讲就是子组件的html内容需要父组件传入,在jsx的加持下,我可以把html像普通的字符串,数字一样传递,所以插槽只需要直接作为props传入就行
基础插槽
在基础插槽中,我们可以使用 children
prop 来接收父组件传递的内容
import React from 'react';
// 容器组件,支持基础插槽
const Container = ({ children }) => {
return (
<div className="container">
<h2>Container Header</h2>
<div className="content">{children}</div>
</div>
);
};
// 使用容器组件
const App = () => {
return (
<Container>
<p>This is the content inside the container.</p>
</Container>
);
};
export default App;
具名插槽
可以通过将不同的 props 传递给子组件来实现。我们可以使用标准的 JSX 语法来实现具名插槽
import React from 'react';
// 容器组件,支持具名插槽
const Container = ({ header, footer, children }) => {
return (
<div className="container">
<div className="header">{header}</div>
<div className="content">{children}</div>
<div className="footer">{footer}</div>
</div>
);
};
// 使用容器组件
const App = () => {
return (
<Container
header={<h1>This is the Header</h1>}
footer={<h2>This is the Footer</h2>}
>
<p>This is the main content area.</p>
</Container>
);
};
export default App;
作用域插槽
允许我们将数据传递到子组件,使得父组件可以控制子组件的渲染。我们可以通过传递一个函数作为 prop 来实现
import React from 'react';
// 容器组件,支持作用域插槽
const ListContainer = ({ items, renderItem }) => {
return (
<ul>
{items.map(item => (
<li key={item.id}>{renderItem(item)}</li>
))}
</ul>
);
};
// 使用容器组件
const App = () => {
const items = [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' },
{ id: 3, text: 'Item 3' }
];
const renderItem = (item) => <span>{item.text}</span>;
return (
<ListContainer items={items} renderItem={renderItem} />
);
};
export default App;
样式操作
class类名设值:必须写为className 类名和样式写在css文件里 必须接受一个字符串
style内联:不能像原生一样写成字符串,必须写成对象
import React from 'react';
import './styles.css'; // 这里引入你的 CSS 文件
const MyComponent = ({ className, style }) => {
return (
<div className={className} style={style}>
Hello, World!
</div>
);
};
// 使用组件
const App = () => {
const customStyle = {
color: 'blue',
fontSize: '20px',
backgroundColor: 'lightgray',
};
return (
<MyComponent
className="my-custom-class" // 在 CSS 文件中定义的类名
style={customStyle} // 使用对象形式的内联样式
/>
);
};
export default App;
CSS Modules
CSS Modules 提供了局部作用域的 CSS,避免类名冲突。
//styles.module.css
.myCustomClass { color: blue; font-size: 20px; padding: 10px; background-color: lightgray; }
import styles from './styles.module.css';
const MyComponent = () => { return <div className={
styles.myCustomClass}>
Hello, World!
</div>;
};
生命周期
在 React 函数组件中,没有传统的生命周期方法 ,React 通过 Hooks 提供了类似的功能,允许你在不同的阶段执行副作用。
import React, { useEffect } from 'react';
const MyComponent = () => {
useEffect(() => {
// 组件挂载时执行
return () => {
// 组件卸载时执行
};
}, []); // 空数组表示只在挂载和卸载时执行
return <div>Hello, World!</div>;
};
useRef
用于获取真实dom和vue中ref一个道理 只不过变成了.current
import React, { useRef } from 'react';
const MyComponent = () => {
const inputRef = useRef(null);
const focusInput = () => {
if (inputRef.current) {
inputRef.current.focus();
}
};
return (
<div>
<input ref={inputRef} type="text" />
<button onClick={focusInput}>Focus Input</button>
</div>
);
};
常用hook
useState
语法: const [state, setState] = useState(initialState)
initialState
是初始状态,可以是任何类型(数字、字符串、对象等)。setState
是更新状态的函数。
这个不做举例了
useEffect
语法:useEffect(() => { … },[dependencies])
dependencies
是一个数组,包含 effect 依赖的值。当这些值发生变化时,effect 会重新执行。如果传入空数组 []
,effect 只会在组件挂载和卸载时执行一次。
不做举例了
useContext
语法:const value = useContext(MyContext)
useContext
不接受第二个参数。它从最近的 <MyContext.Provider>
中获取当前上下文值
useReducer
用法: const [state, dispatch] = useReducer(reducer, initialState);
第二个参数: initialState
是 reducer 的初始状态,通常是一个对象或基本数据类型。
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
const Counter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
);
};
useMemo
用法:const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
dependencies
是一个数组,当数组中的任意一个值改变时,computeExpensiveValue
才会重新计算并返回新值
可以提高性能 记忆函数 也就是保存结果,组件多次渲染直接取缓存结果就行
import React, { useMemo } from 'react';
const ExpensiveCalculation = ({ num }) => {
const result = useMemo(() => {
// 进行复杂计算
return num * 2; // 示例计算
}, [num]); // 依赖于 num
return <div>Result: {result}</div>;
};
useCallback
用法: const memoizedCallback = useCallback(() => { }, [dependencies]);
第二个参数: dependencies
是一个数组,当数组中的任意一个值改变时,返回新的回调函数。
更上面一样 就是保存函数
import React, { useState, useCallback } from 'react';
const Button = React.memo(({ onClick }) => {
console.log('Button Rendered');
return <button onClick={onClick}>Click Me</button>;
});
const App = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]); // 依赖于 count
return (
<div>
<Button onClick={handleClick} />
<p>Count: {count}</p>
</div>
);
};
高阶组件
高阶组件(Higher-Order Component,简称 HOC)是 React 中一种用于复用组件逻辑的模式。它本质上是一个函数,接受一个组件作为参数,并返回一个新的组件。高阶组件可以用于许多场景,例如:状态管理、数据获取、权限控制等。
import React from 'react';
// 原始组件
const MyComponent = ({ extraInfo }) => (
<div>
<p>这是我的组件。</p>
<p>{extraInfo}</p>
</div>
);
// 高阶组件
const withExtraInfo = (WrappedComponent) => {
return (props) => {
const extraInfo = "附加信息:这是来自高阶组件的内容!";
return <WrappedComponent {...props} extraInfo={extraInfo} />;
};
};
// 使用高阶组件
const EnhancedComponent = withExtraInfo(MyComponent);
const App = () => (
<div>
<EnhancedComponent />
</div>
);
export default App;
什么时候使用
组件是既包含了ui界面的复用,也包含了逻辑的复用,高阶组件只是复用操作逻辑,运算,类似于vue中mixin的用途,当我们发现某个操作逻辑,或者某个运算经常出现的时候,我们可以提取为高阶组件
react性能问题和优化
Vue的因为是在get和set里触发更新
Vue在get部分有一个重要的操作-依赖收集
这样我们在更改了数据后,只会更新用到了这个数据的地方做到最小的更新范围
React的更新是调用方法时触发的,并没有依赖收集的过程所以他会更新整个组件树也就是会把子组件一起更新即使更新的数据和子组件没有任何关系
所以React最大的一个性能问题就是-React的某个组件的更新会连带着,他的子组件一起更新。所以我们需要解决这个问题让子组件只做合理的更新
React的时间切片
Vue有依赖收集,做到了最小的更新范围,而React没有做这个事情。所以React要更新,就会有很大的diff算法比对和计算工作
这大的更新量,虚拟dom比对和计算会花很大时间,这样可能会阻塞住浏览器的工作,导致页面长时间白屏
React为了解决这个问题选择另一种策略-时间切片,也就是先计算一部分更新,然后让渡给渲染进程 然后再进行下一步更新我从使用者的角度 就不会出现长时间白屏了。
fiber
为了支持这种切片,我们需要把更新化成一个个单元,然后我们也必须有回复上一次计算进度的能力
所以react设计一种数据结构-fiber
每一个组件会被转化为一个fiber结构的对象,组成一个个单元。Fiber让我们有了回复上次中断的计算进度的能力
类似于vue中的虚拟节点vnode, Both Fiber 和 vnode 都是对真实 DOM 的抽象,旨在提高性能和效率。它们允许框架在内存中处理组件树,而不是直接操作 DOM。
避免父组件数据导致子组件更新
React.memo
子组件 让它缓存
我们特别注意父组件传入的方法,对象,数组这样的引用类型
用useCallback包裹传递给子组件的方法
state对象,数组数据,要用useMemo包裹起来
import React, { useState, useCallback, useMemo } from 'react';
// 子组件
const ChildComponent = React.memo(({ handleClick, data }) => {
console.log('ChildComponent rendered');
return (
<div>
<button onClick={handleClick}>Click Me</button>
<ul>
{data.map((item, index) => (
<li key={index}>{item}</li>
))}
</ul>
</div>
);
});
// 父组件
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [items, setItems] = useState(['Item 1', 'Item 2']);
// 使用 useCallback 包裹传递给子组件的方法
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
// 使用 useMemo 包裹传递给子组件的数组数据
const memoizedItems = useMemo(() => {
return items;
}, [items]);
return (
<div>
<h1>Count: {count}</h1>
<ChildComponent handleClick={handleClick} data={memoizedItems} />
</div>
);
};
export default ParentComponent;
如果对你有所帮助的话就点个关注吧,会持续更新技术文章
更多推荐
所有评论(0)