React基础知识 --个人学习笔记个人学习笔记,记录学习使用,别点11111111111111111111111111
react学习前置准备
常用到的网站
| 学习网站 | 网址 | 种类 |
|---|---|---|
| React中文 | React中文文档 | 文档 |
| React Router | React Router6 中文文档 | 路由 |
| React Router | React Router | 路由 |
| zustand | zustand | 状态管理 |
| TanStack Query | TanStack Query 中文文档 | 请求管理,路由 |
| redux | RTK | 状态管理,请求管理 |
| Fetch | Fetch - MDN | 请求 |
| fontawesome图标 | fontawesome图标 | 图标 |
| AntD | Ant Design | 图标 |
| Next.js | Next.js | 类似框架 |
| solidjs | solidjs | 类似框架 |
| 生态圈 | awesome-react | 生态圈 |
第一步安装vite
在项目中初始化vite
在命令行中全局安装vite
npm i -g vite
npm create vite
在vite中选择react的基础配置
根据提示选择对应的框架即可
在跳转到建立的项目文件夹中
cd ***(创建的项目文件夹的名字)
配置vite的依赖包
npm i
执行对应的vite命令即可
npm dev
React的基本知识
(在vite工具的帮助下,不需要注意这些)
基本的React了解
| 名称 | 介绍 |
|---|---|
| babel.min.js | 主要将Jsx转换成js格式 |
| react.development.js | React核心库 |
| React-Dom.delelopment.js | 用于支持React操作Dom |
引入的顺序
- 先引入核心库react.development.js
- 在引入react-dom,用于支持react操作dom
- 最后引入babel,用于将Jsx转换成js,让后创建dom
- 如果使用vite等打包脚手架工具,则不需要以上步骤
写react的准备
-
写react代码要注明
<script type="text/babel">在末尾引入,这样浏览器才知道写的是Jsx的代码
-
控制台上favicon.ico 的警告提醒,在项目文件的根目录中放一张ico偏爱图标即可,图标名字一定要为favicon.ico,不然浏览器查询不到
了解react的简单使用方法
开始准备容器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
//引用react
<script type="text/javascript" scr="../js/react.development.js"></script>
<script type="text/javascript" scr="../js/react-dom.development.js"></script>
<script type="text/javascript" scr="../js/babel.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="test"></div>
//准备容器,用于放置react渲染的dom
</body>
</html>
创建虚拟Dom,并渲染到界面上
<script type="text/babel">
//创建虚拟dom
const vm=<h1>hello,react</h1>
//也可以写成括号包裹形式,便于观看,如下
const vm=(
<h1>hello,react</h1>
)
//通过js创建虚拟dom,这样不用引入babel.min.js
const vm= react.createElement('h1',{id:'test'},'hello,react')
//渲染虚拟dom到页面
ReactDom.render(vm,document.getElentById"test")
</script>
jsx最终在浏览器中呈现的格式都是js模式,如上方格式,不过常用的是其语法糖模式
如果用js创建虚拟dom,第一部分写标签名,第二部分写标签属性,以对象的形式写,可在内写多个属性,第三部分写标签内容
标签内容中可嵌套
const vm= react.createElement('h1',{id:'test'},react.createElement('span',{id:test1},'你好'))
关于虚拟DOM
-
本质是object类型的对象
-
因为虚拟dom在react的内部使用,不需要太多的属性,所以虚拟dom的属性较少,所以比较"轻",速度更快
-
虚拟DOM最终会被react转换成真实DOM,呈现在页面
-
jsx是resct定义的一种类xml的js扩展语法
xml语法
<student>
<name>tom</name>
<age>18</age>
</student>
xml语法现在基本被json语法替代
{
'name':'tom',
'age':'18'
}
jsx的语法规则
-
定义虚拟DOM时不要写写引号
-
标签中混入js表达式时要用{}号包裹
-
样式的类名指定时不要用class,用className,避免与js语法混搅
-
jsx中css的内联样式要用style={{key:value}}的双括号形式去写,原本js中要引用css内联样式应写style="key:value"
-
虚拟标签dom只用一个根标签,一个根标签对应一个虚拟DOM,如果有多个同级虚拟DOM,要用一个标签将其包裹
-
标签必须闭合,所以像input这种单标签,要写成以下形式
<input type='text'/> -
标签首字母,如果是小写字母,则将标签转成html的同名标签元素;如果是大写字母开头,则将其渲染成react组件
小例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
//引用react
<script type="text/javascript" scr="../js/react.development.js"></script>
<script type="text/javascript" scr="../js/react-dom.development.js"></script>
<script type="text/javascript" scr="../js/babel.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="test"></div>
//准备容器,用于放置react渲染的dom
</body>
<script type="text/babel">
const myId='zs'
const myData='hello'
//创建虚拟dom
const vm=(
<div>
//假设title为假设外联式的css
<h2 className='title' id={myId.tolowerCare()}>
<span style={{color:'white',font:'29px'}}>{myData.tolowerCare()}</span>
</h2>
<input type='text'/>
</div>
)
</script>
</html>
简单理解:
- 表示固定的字符串就用正常的 ' ' 和 " " 的引号,如 className='title'
- css内联式用{{ }}号
- 自定义的js表达式就用{ }号
- 类名用className
- 原因解析:原js语法中标签都是' '形式,都是字符串,没有变量的概念;所以jsx语法中用js表达式(本质上是变量赋值)时用的是{ }号;而css内联式是键值对的形式,便不可以用单括号,便用{{ }}双括号来表示
小例子 动态将数组中的名称建立到列表标签中
<script type="text/babel">
const data=['zs','ls','ww']
const vm=(
<div>
<ul>
{data.map((item,index)=>{
return <li key={index}>{item}</li>
//react为了diff算法,其中的标签中都要赋予唯一的key值
//例子中的key值赋予的有问题,在进行数组拼接时会出现key值重复
}
)}
</ul>
</div> )
</script>
代码判断执行的小技巧
回调函数存在即执行,没有就忽略
cb && cb();
Json 存在参数就放进去,不存在就忽略
body:body?JSON.stringify({data:body}):null,
函数式组件
<script type="text/babel">
function Dome(){
return <h2>函数式组件</h2>
}
//渲染虚拟dom到页面
ReactDom.render(<Dome/>,document.getElentById"test")
</script>
该自定义函数this本应指向window,但是经过babel翻译后,开启了严格模式,进制来this指向window指向window,所以查看时,该this为undefined
严格模式开启 "use strict"
- react解析组件标签,找到Component组件
- 发现是组件是函数定义,随后调用该函数,将虚拟Dom转为真实dom
- 呈现在页面中
React事件处理
react中的事件处理的两个重点
通过onXxx属性指定处理函数,驼峰式命名
a.React中的事件都是重新封装过的。不是js中的原生DOM事件-----------为了兼容性
b.React中的事件都是通过事件委托到组件最外层的元素的-----------为了高效
通过event.target获得事件
通过event.target得到发生事件的DOM对象 避免过渡使用ref
import React, { useRef, useCallback } from "react";
const Person = () => {
// 使用 useRef 创建 ref
const myRef = useRef(null);
// 使用 useCallback 创建事件处理函数
const showData = useCallback(() => {
if (myRef.current) {
alert(myRef.current.value);
}
}, [myRef]);
const showData2 = useCallback((event) => {
alert(event.target.value);
//通过event.target得到发生事件的DOM对象
//因为该组件的数据,点击事件都是在自身上,所以事件通过event.target得到发生事件的DOM对象上的数据即可,可以不用ref
}, []);
return (
<div>
<input type="text" ref={myRef} placeholder="点击显示数据" />
<button onClick={showData}>点击上方显示数据</button>
//通过onXxx属性指定处理函数,驼峰式命名
<br />
<input type="text" onBlur={showData2} placeholder="失去焦点显示数据" />
<br />
</div>
);
};
export default Person;
受控组件和非受控组件
在React中,受控组件和非受控组件是常见的两种组件形式。它们的区别在于组件是否自己管理自己的状态。
受控组件
- 受控组件的状态(例如输入框中的值)由React组件的state管理。
- 当用户在受控组件中输入内容时,React组件的state会更新,因此文字也会相应地更新。
- 开发人员可以在onChange事件中更新状态,以响应用户输入。
非受控组件
- 非受控组件的状态由DOM管理,而不是由React组件的state管理。
- 当用户在非受控组件中输入内容时,DOM自己管理了输入值。
- 开发人员可以使用ref(引用)来访问组件的值。
总结
- 当开发人员需要访问和控制组件中的值时,应该使用受控组件。
- 当用户输入的值不需要在组件上进行操作时,可以使用非受控组件。
import React, { useState, useRef } from "react";
const Person = () => {
// 创建refs用于非受控组件通过操控DOM的形式更新值
const myRef = useRef(null);
const myRef1 = useRef(null);
// 受控组件的状态变量
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
// 非受控组件处理表单提交
const handleSubmit = (event) => {
event.preventDefault();
// 使用refs获取输入框的值
const usernameValue = myRef.current.value;
const passwordValue = myRef1.current.value;
confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);
};
// 受控组件保存用户名
const saveUsername = (event) => {
setUsername(event.target.value);
};
// 受控组件保存密码
const savePassword = (event) => {
setPassword(event.target.value);
};
return (
<form action="" onSubmit={handleSubmit}>
用户:
{/* 受控组件通过setState的形式控制输入和更新值 */}
<input type="text" name="username" onChange={saveUsername} value={username} /><br />
密码:
{/* 受控组件通过setState的形式控制输入和更新值 */}
<input type="text" name="password" onChange={savePassword} value={password} /><br />
<button>登录</button>
</form>
);
};
export default Person;
高阶函数的柯西里化
对函数代码的柯里化优化
import React, { useState, useRef } from "react";
const Person = () => {
const [user, setUser] = useState({
username: '',
password: '',
})
const myRef = useRef();
const myRef1 = useRef();
const handleSubmit = (event) => {
event.preventDefault();
const usernameValue = myRef.current.value;
const passwordValue = myRef1.current.value;
confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);
};
const saveForm = (dataType) => {
// 用参数传递类型,从而区分函数的作用
// 让该函数的返回值返回所需要的函数
return (event) => {
setUser({
...user,
[dataType]: event.target.value
//方括号可以在对象字面量中动态的解析key
//不用方括号就会出现,dataType:123
}
);
}
};
return (
<form action="" onSubmit={handleSubmit}>
用户:
<input
type="text"
name="username"
onChange={saveForm('username')}
value={user.username}
ref={myRef}
/>
<br />
{/* 如果是onChange={saveForm},则就是直接调用saveForm函数。 */}
{/* 如果是onChange={saveForm('username')},则就是直接调用saveForm函数的返回值,即一个处理用户名更新的函数。 */}
密码:
<input
type="text"
name="password"
onChange={saveForm('password')}
value={user.password}
ref={myRef1}
/>
<br />
<button>登录</button>
</form>
);
};
export default Person;
高阶函数定义
1.接收的参数是一个函数
2.被调用的返回值是一个参数
对函数代码的非柯里化优化
import React, { useState, useRef } from "react";
const Person = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const myRef = useRef();
const myRef1 = useRef();
const handleSubmit = (event) => {
event.preventDefault();
const usernameValue = myRef.current.value;
const passwordValue = myRef1.current.value;
confirm(`你输入的账号是${usernameValue},你输入的密码是${passwordValue}`);
};
const saveForm = (dataType, event) => {
// 通过参数传递类型,从而区分函数的作用
// 在事件处理函数中更新对应的状态
const { value } = event.target;
if (dataType === 'username') {
setUsername(value);
} else if (dataType === 'password') {
setPassword(value);
}
};
return (
<form action="" onSubmit={handleSubmit}>
用户:
<input
type="text"
name="username"
onChange={(event) => saveForm('username', event)}
value={username}
ref={myRef}
/>
<br />
//和上面的柯里化差不多的道理,通过调用回调函数,在回调函数中再去调用别的函数
密码:
<input
type="text"
name="password"
onChange={(event) => saveForm('password', event)}
value={password}
ref={myRef1}
/>
<br />
<button type="submit">登录</button>
</form>
);
};
export default Person;
函数式组件的props
props
传递过来的属性是只读的,如果想修改可以弄一个值接受一下,但是不能直接操作props
Object.freeze(obj)冻结,不能修改,不能增删,不能劫持
Object.seal(obj)密封,能修改,不能增删,不能劫持
Object.preventExtensions(obj)不可扩展,能修改,不能新增
import React from "react";
import PropTypes from 'prop-types';
Person.propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
}
//对函数组件的props做限制,因为不是类,所以不能用类的静态方法,只能使用函数Person中方法
//如果使用ts,该插件可以不再使用
Person.defaultProps = {
sex: "男",
age: 18,
};
//与上同理
function Person(props){
//创建函数并接受props
const {name,sex,age}=props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
export default Person;
props.children
子组件中如果不存在内容,props.children,就为null
子组件中如果存在一个标签,props.children,就为该标签的对象,可以在大胡子语法中直接渲染
props一般是用来传值,而props.children 是 React 中的一个特殊属性。它允许你在组件标签内传递子元素。例如:
<MyComponent>
<p>Hello, world!</p>
</MyComponent>
在 MyComponent 组件内部,可以通过 props.children 属性来访问子元素 <p>Hello, world!</p>。这样,MyComponent 组件就会将子元素 <p>Hello, world!</p> 显示在自己的内容区域中。
function MyComponent(props) {
return (
<div>
{props.children}
</div>
);
}
子组件中如果存在多个标签,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置
<MyComponent>
<p>Hello, world!</p>
<p>你好,世界</p>
</MyComponent>
function MyComponent(props) {
return (
<div>
{props.children[1]}//你好,世界
{props.children[0]}//Hello, world!
</div>
);
}
子组件中存在多个标签,还可以给组件子元素加上一个slot='???'的属性作为名字,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置
<>
<Hello>
<p slot="0">123</p>
<p slot="1">234</p>
</Hello>
</>
<>
<div>
{props.children.map((item) => {
if(item.props.slot==="1"){
//注意item.props,是props.children.props不是props
return item
}
})}
{props.children.map((item) => {
if(item.props.slot==="0"){
//注意item.props,是props.children.props不是props
return item
}
})}
//这么写也行
<>
<div>
{props.children.map((item) => {
return item.props.slot==="1"&&item
})}
</div>
</>
</div>
</>
子组件被多次调用,还可以给组件加上一个slots='???'的属性作为组件名字,props.children就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置 一般是弄个变量在上面接收好,再在下面使用,避免下面的代码太长
<>
<Hello>
<p slot="0">123</p>
<p slot="1">234</p>
</Hello>
<Hello slots="3">
<p >456</p>
</Hello>
</>
<>
<div>
{
props.slots=='3'&&props.children
}
{
props.slots==null&&props.children
}
</div>
</>
props.render
在React中,一个组件的props是通过props属性传递给它的,可以使用props来获取组件传递的数据。而render函数是一种用来创建组件的方法,它可以将组件渲染到页面上。可以将render函数作为组件的一个属性传递给组件,并在组件的render方法中调用该属性。这样,可以实现动态生成DOM元素或者执行一些逻辑的功能。
例如,可以定义一个MyComponent组件,并将一个带有render函数的props传递给它,如下所示:
<MyComponent render={() => <span>Hello World</span>} />
function MyComponent(props) {
return <div>{props.render()}</div>;
}
在上面的例子中,MyComponent组件接收一个名为render的props,它是一个函数,返回一个带有Hello World文本的span元素。在MyComponent组件的render方法中,调用了props.render函数,并将其返回的元素渲染到了页面上。
通过这种方式,可以在组件内部动态生成DOM元素,并且可以根据需要实现更为复杂的功能。
示例
1.父组件Dome将render={() => {return <A>}}作为Props
2.传给子组件B,<B render={() => {return <A>}}/>
3.子组件B中,接收作为返回值返回的孙组件A,() => {return <A>}
import { useState,useContext} from "react";
import { createContext } from 'react';
export const MyContext = createContext({
Name:'jack',
age:20
});
function Hello() {
const [Name, setName] = useState('jack');
const [age, setAge] = useState(18);
// useEffect 可以用来模拟类组件的某些生命周期方法,但在这个例子里我们不需要它
// change 函数用于切换名字
const change = () => {
setName(prevName => (prevName === 'jack' ? 'Tom' : 'jack'));
};
// 渲染函数
const renderA = () => <A />;
// 渲染逻辑
console.log('Dome 组件渲染了');
return (
<div className="parent" style={{ width: '500px', height: '90vh', background: 'gray',margin: '10px',padding: '10px',}}>
<h3>我是 hello 组件</h3>
<h4>我的名字是:{Name}</h4>
<button onClick={change}>点击换名</button>
<MyContext.Provider value={{ Name, age }}>
{/*
重点部分:通过 render 属性传递一个函数,该函数返回 <A/> 组件
B 组件里提前留好 {this.props.render()} 插槽
render={() => {return <A/>}} 里的 <A/> 就是插件
这样就可以在设置好之后,不经过 B 组件,就可以通过插槽,在 Dome 组件里修改 B 组件中的子组件
*/}
<B Name={'固定的jack'} render={renderA} />
</MyContext.Provider>
</div>
);
};
const B = ({ Name, render }) => {
console.log('B组件渲染了');
return (
<div className="child" style={{ width: '400px', height: '50vh', background: 'skyblue', margin: '10px', padding: '10px',}}>
<h3>我是B组件</h3>
<h4>我的名字是{Name}</h4>
{/* B组件里提前留好插槽 */}
{render()}
</div>
);
};
const A = () => {
console.log('A组件渲染了');
const { Name, age } = useContext(MyContext);
// 假设 AA 组件已经定义好了,并且可以在这里直接使用
// 如果 AA 组件也需要使用上下文或者具有其他状态,那么它也应该被重写为函数式组件
return (
<div className="grand" style={{ width: '300px', height: '200px', background: 'orange' ,margin: '10px',padding: '10px',}}>
<h3>我是A组件</h3>
<h4>我爷爷的名字是{Name},年龄{age}</h4>
<B Name={Name} render={() => <span>我又在A中插入了</span>} />
</div>
);
};
const AA = () => {
console.log('AA组件渲染了');
const {Name,age}=useContext(MyContext)
return (
<div className='grand'style={{width:'250px',height:'100px',background:'pink'}}>
<h3>我是AA组件</h3>
<h4>我爷爷的名字是{Name},年龄{age}</h4>
</div>
);
}
export default Hello;
export default Dome;
两者的区别
props.render是一个函数,它接收一些参数并返回一个组件。通常用于在父组件中动态地渲染子组件。例如:
function Parent(props) {
return (
<div>
{props.render()}
</div>
)
}
function Child() {
return (
<h1>Hello, world!</h1>
)
}
function App() {
return (
//在标签内属性定义一个回调
<Parent render={() => <Child />} />
)
}
props.children表示组件的子元素。它可以是一个组件,也可以是多个组件。React会将children作为props传递给父组件,从而允许我们在组件内部使用组件。例如:
function Parent(props) {
return (
<div>
{props.children}
</div>
)
}
function Child1() {
return (
<h1>Hello, world!</h1>
)
}
function Child2() {
return (
<p>This is a paragraph</p>
)
}
function App() {
return (
//在标签内容定义组件
<Parent>
<Child1 />
<Child2 />
</Parent>
)
}
因此,props.render用于动态地渲染子组件,而props.children用于将已经存在的组件作为父组件的子元素。
函数式组件的useState
useState的使用
import React from 'react'
function Index () {
//类式组件改变状态的样式,和下面函数式组件对比学习
// state={count:0}
//add=() => {
//this.setState({count:count+1})
//}
//对状态的数组进行解构赋值,数组第一个参数是状态名;第二个参数是改变状态的函数。相当于setState
const [count,setCount]=React.useState(0)
const [name,setName]=React.useState('jack')
function add() {
//在事件回调的函数中触发对应状态的函数
setCount((count) => {
return count+1}
)
}
function changeName() {
//像这种简单的改变,直接向set***中传入参数即可
setName('tom')
}
return (
<div>
<h1>当前求和为{count}</h1>
<h1>{name}</h1>
<button onClick={add}>点击加一</button>
<button onClick={changeName}>点击换名</button>
</div>
)
}
export default Index
闭包和消息队列的骚扰
import { useState } from "react";
function Hello() {
const [count, setCount] = useState(0);
const [name, setName] = useState("jack");
// 函数式组件每次渲染,都是函数的重新创建,所以就产生了闭包问题
//useState,只有第一次设的初始值会生效,后续的值都是setState函数的返回值
//也是因为useState只有第一次会执行,所以像对初始数据处理的行为,可以直接在useState里写个回调处理
//重新创建的组件函数拿到setState函数的返回值,实现界面上数值的更新
//因为新值是新创建的组件函数拿到的,所以在这里不经过处理拿到的都是没修改前的旧值
function add0() {
console.log("因为闭包只能显示旧值,会比界面上现实的值小一:",count);
setCount(count + 1);
}
function add1() {
for (let i = 0; i < 10; i++) {
setCount(count + 1);
}
}
//结果并不会加十,因为这里每次只能读到旧值
//所以虽然执行了10次,但是每次读取都是0,进行了十次0+1,结果就为1
function add2() {
for (let i = 0; i < 10; i++) {
setCount((count) => {
return count + 1;
});
}
}
//结果会加十,因为这里每里用了回调函数实现了闭包,使值在回调函数中被记录了下来
//所以结果就为11
function changeName() {
setName("tom");
}
return (
<div>
<h1>当前求和为{count}</h1>
<h1>{name}</h1>
<button onClick={add0}>闭包,点击加一</button>
<button onClick={add1}>错误的点击加十</button>
<button onClick={add2}>正确的点击加十</button>
<button onClick={changeName}>点击换名</button>
</div>
);
}
export default Hello;
结论
state简单的修改就用直接用
涉及到对值重复修改就要考虑闭包问题,最好使用回调函数进行闭包记值
setStata修改出现的问题
import { useState } from "react";
function Hello() {
const [count, setCount] = useState({
boyNum: 100,
girlNum: 100,
});
function addBoy() {
setCount({
...count,
//setState,每次都是将值重新赋值给新渲染的组件函数,所以不能单独渲染某一个值
//要将原本的值全部展开,再修改某一个值,不然那些值就会丢失
//如果一开始就想只修改特定的值,建议直接那些值拆开,不要放在一个对象
boyNum: count.boyNum + 1
})
}
function addGirl() {
setCount({
...count,
girlNum: count.girlNum + 1
})
}
return (
<div>
<h2>当前人数为{count.boyNum + count.girlNum}</h2>
<h3>男生人数为{count.boyNum}</h3>
<h3>女生人数为{count.girlNum}</h3>
<button onClick={addBoy}>男生加1</button>
<button onClick={addGirl}>女生加1</button>
</div>
);
}
export default Hello;
函数组件的useEffect
useEffect的用法
讲解 因为函数式组件没有生命周期,所以用useEffect来模仿生命周期
常用于 1.发网络请求 2.设置订阅 3.启动定时器 4.手动更改真实DOM
React.useEffect(() => {
//在这里执行副作用操作,如挂载后,更新后的定时器开启,状态改变之类的
...
//在这里执行收尾操作,如卸载组件后的清除定时器
return () => {
...
};
//[]里是检测对象。如果该数组没写,则是检测所有的生命周期行为;
//如果有数组但是为空的话,就是只检测挂载行为
//如果有数组而且为状态的话,将检测该状态的生命周期
}, []);
示例
import React from "react";
import root from "../main";
function Index() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
//组件挂载时启动定时器
let timer = setInterval(() => {
setCount((count) => {
return count + 1;
});
}, 1000);
//组件删除时,清理定时器
return () => {
clearInterval(timer);
};
}, []);
//点击事件触发的函数
function unm() {
root.unmount();
}
return (
<>
<h1>计时为:{count}</h1>
<button onClick={unm}>点击卸载</button>
</>
);
}
export default Index;
模拟生命周期
- 在第一次渲染完毕执行 在组件每次更新后也会执行 所以它可以获得最新的状态值
useEffect(() => {
consloe.log("ok")
});
- 加了空数组它就只会在组件第一次渲染完成才会更新
useEffect(() => {
consloe.log("ok")
},[]);
- 加了有值数组它在组件第一次渲染完成,和数组值发生变化时都会更新
useEffect(() => {
consloe.log("ok")
},[x,y]);
- 加了返回的回调函数,会在effect执行之后再执行
useEffect(() => {
let timer = setInterval(() => {
setCount((count) => {
return count + 1;
});
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
对useEffect的理解--超级重要
- 只有一个回调的useEffect普通函数差不多,在组件函数重新执行时被创建及调用
- 只有空数组的useEffect就和useState差不多,在组件函数第一次执行时被调用,后面不再调用
- 有数值数组的useEffect就在数值绑定,在setState时被调用
- 带返回的effect也是一个道理,在useEffect内容执行完,返回一个结果或者回调,同时组件函数被重新渲染,先执行上一次留些的回调,然后下一个useEffect开始执行
- 难怪叫它副作用,,,没理解透,用的不好,后患无穷
useEffect对异步的处理
useEffect中只能接收函数,不能接受promise,所以调用时尽量用then处理
useEffect(() => {
async function fetchData() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts");
const data = await response.json();
flushSync(() => {
setBoy(data[7].id);
});
}
fetchData();
},[]);
useEffect(() => {
function fetchData() {
fetch("https://jsonplaceholder.typicode.com/posts")
.then((response) => response.json())
.then((data) => {
flushSync(() => {
setBoy(data[10-1].id);
});
});
};
fetchData();
}, []);
UseLayoutEffect
useLayoutEffect 是 React 提供的一个 Hook,用于在 DOM 变更之后同步执行副作用。它与 useEffect类似,但有一些重要的区别。useEffect是在渲染完成之后执行副作用
而 useLayoutEffect则是在旧 DOM 变更完成之后、优先同步执行useLayoutEffect中的返回及内容。这意味着它会在浏览器绘制之前执行,可以阻塞浏览器渲染,因此应该谨慎使用。
useLayoutEffect 的主要作用是在执行完 DOM 变更后立即读取 DOM 布局信息,然后同步更新其他状态或进行其他 DOM 操作。它常用于计算元素的尺寸、位置等,并根据这些信息进行后续操作。
在使用 useLayoutEffect 时,应注意避免执行长时间运行的任务,以免阻塞页面渲染,也要留意其可能引起的性能问题。如果不需要同步执行副作用,可以使用 useEffect 来代替。
使用 useLayoutEffect 的示例代码如下:
import React, { useLayoutEffect, useRef } from 'react';
function MyComponent() {
const elementRef = useRef(null);
useLayoutEffect(() => {
const element = elementRef.current;
const rect = element.getBoundingClientRect();
console.log('Element width:', rect.width);
}, []);
return <div ref={elementRef}>Hello, World!</div>;
}
在上述示例中,useLayoutEffect 在渲染完成之后同步执行副作用,获取了 <div> 元素的尺寸信息,并将其打印到控制台上。
。
uselnsertionEffect
useInsertionEffect 是为 CSS-in-JS 库的作者特意打造的。除非你正在使用 CSS-in-JS 库并且需要注入样式,否则你应该使用 useEffect 或者 useLayoutEffect
useInsertionEffect,它允许开发人员在元素被插入到DOM中时执行一些操作。在React中,元素被插入到DOM中通常是在组件挂载时发生的。该回调函数会在元素被插入到DOM中时被调用。由于useInsertionEffect只能在元素插入到DOM中时执行操作,因此它可能无法满足某些特殊的需求,例如在元素被移除时执行操作。相当生命周期挂载完成
function Component() {
useInsertionEffect(() => {
document.querySelector('.app').classList.add('red');
});
return (
<div className="app">Hello World!</div>
);
}
import { useInsertionEffect } from 'react-insertion-effect';
function MyComponent() {
useInsertionEffect(() => {
const handleScroll = () => {
const scrollPosition = window.innerHeight + window.scrollY;
const documentHeight = document.body.offsetHeight;
if (scrollPosition >= documentHeight) {
// 加载更多内容
}
};
window.addEventListener('scroll', handleScroll);
return () => {
window.removeEventListener('scroll', handleScroll);
};
});
return (
// 组件内容
);
组件中使用了useInsertionEffect,会在挂载完成时执行,在回调函数中,我们定义了一个handleScroll函数,用于检查是否需要加载更多内容。我们还添加了一个事件监听器,当用户滚动页面时,handleScroll函数就会被调用。最后在回调函数的返回值中移除了事件监听器,以避免内存泄漏。
函数组件的ref
useRef
import React from "react";
function Index() {
const [count, setCount] = React.useState(0);
const myRef=React.useRef()
function show() {
confirm('输出:'+myRef.current.value)
myRef.current.value=''
}
return (
<>
<input type="text" ref={myRef}/><br />
<button onClick={show}>点击显示</button>
</>
);
}
export default Index;
forwardRef
可以用来指定组件向外部暴露的 ref
useRef() 无法直接去获取 react 组件的 dom 对象, 因为一个 react 组件中可能含有多个 dom 对象 React 也不知道要给你谁,可以用 React.forwardRef() 可以用来指定组件向外部暴露的 ref
在 React 中可以通过 forwardRef 来指定要暴露给外部组件的 ref:
const MyButton = forwardRef((props, ref) => {
return <button ref={ref}>自定义按钮</button>
});
上例中,MyButton 组件将 button 的 ref 作为组件的 ref 向外部暴露,其他组件在使用 MyButton 时,就可以通过 ref 属性访问:
<MyButton ref={btnRef}/>
可以把 整个 DOM 对象 暴露出来,缺点是不可控,希望可以只暴露操作方法,受限制去操作
const ChildComponent = React.forwardRef((props, ref) => {
const handleClick = () => {
// 子组件内部的一些逻辑
}
return (
<button ref={ref} onClick={handleClick}>Click Me</button>
)
})
const ParentComponent = () => {
const childRef = useRef()
const handleButtonClick = () => {
childRef.current.click()
}
return (
<div>
<ChildComponent ref={childRef} />
<button onClick={handleButtonClick}>Click Child Button</button>
</div>
)
}
useImperativeHandle
减少组件对 DOM 对象的直接操作
通过 useImperativeHandle 可以手动的指定 ref 要暴露的对象,比如可以修改 MyButton 组件如下:
const MyButton = forwardRef((props, ref) => {
useImperativeHandle(ref,()=> {
return {
name:'孙悟空'
};
});
return <button>自定义按钮</button>
});
useImperativeHandle 的第二个参数是一个函数,函数的返回值会自动赋值给 ref(current 属性)。上例中,我们将返回值为 {name:'孙悟空'} ,当然返回孙悟空没有什么意义。实际开发中,我们可以将一些操作方法定义到对象中,这样可以有效的减少组件对 DOM 对象的直接操作。
const MyButton = forwardRef((props, ref) => {
const btnRef = useRef();
// useImperativeHandle 可以用来指定ref返回的值
useImperativeHandle(ref,()=> {
// 回调函数的返回值,会成为ref的值
return {
setDisabled(){
btnRef.current.disabled = true;
}
};
});
return <button ref={btnRef}>自定义按钮</button>
});
//App.js
const App = () => {
const btnRef = useRef();
const clickHandler = () => {
btnRef.current.setDisabled();
};
return <div>
<MyButton ref={btnRef}/>
<button onClick={clickHandler}>点击</button>
</div>;
};
函数是组件的useReducer
const [state, dispatch] = useReducer(reducer, initialArg, init);
第一个参数 reducer 是函数 (state, action) => newState,接受当前的 state 和操作行为。第二个参数 initialArg 是状态初始值。第三个参数 init 是懒惰初始化函数。
使用 useReducer 时,状态(state)仍是保存在组件内部的。useReducer 返回的数组中的第一个元素是当前的状态,而第二个元素是一个 dispatch 函数,用于触发状态的更新。状态是组件的本地状态,与其他组件是隔离的
用官方的一个例子看一下使用方式
import React, { useReducer } from 'react';
// 定义一个 reducer 函数
const counterReducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
// 你可以添加更多 case 来处理其他动作
default:
throw new Error();
}
};
function Counter() {
// 初始化 state
const initialState = { count: 0 };
// 使用 useReducer Hook
const [state, dispatch] = useReducer(counterReducer, initialState);
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
</div>
);
}
export default Counter;
dispatch 的参数是 reducer 的 action,reducer 函数根据传入的 action 执行某些逻辑,最后返回的就是新状态。
useReducer的使用实例
-
dispatch 向下传递, dispatch 可以代替 callback 的方式向子组件传递,进行从下向上的更新。优点在于 dispatch 在更新时不会重新定义,或多或少的减少了一点重新定义 callback 函数的开销,还便于子组件根据 props 判断是否需要更新。
假如层级过深,还可以搭配 context 使用,此时使用 dispatch 代替 callback 优势更明显。因为 dispatch 在 render 时不变,不会引起使用 context 的组件执行无意义的更新。
-
批量更新 react 对事件之外的更新不会批量处理,使用 reducer 可以避免此类问题,代码如下:
const reducer = (state, action) => { switch(action.type) { case 'update': return { ...state, data: action.payload.data, loading: false, } default: return state; } } // 事件外触发 dispatch({ type: 'update', payload: { data }})// 或者干脆 const reducer = (state, newState) => { return {...state, ...newState} } const [state, dispatch] = useReducer(reducer, {loading: true, data: null, something: ''}) // 触发 dispatch({loading: false})其本质还是将 state 放到同一个地方处理,只是比 useState 更具可读性。
forceUpdate时useState 和 useReducer
useState 和 useReducer 在值不变时会跳过更新
useState举例
假设有一个组件,其中使用了 useState 和 useEffect,每次点击按钮时会将 count 值加 1:
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count changed');
}, [count]);
const handleClick = () => {
setCount(count + 1);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Add Count</button>
</div>
);
}
export default MyComponent;
在此示例中,我们使用 useEffect 监听 count 变化,并在控制台输出一条日志。如果我们每次点击按钮时,count 的值都会改变,从而导致 useEffect 执行。
但是,如果我们使用 forceUpdate,因为 count 的值没有改变,所以 useEffect 不会执行。
import React, { useState, useEffect } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log('count changed');
}, [count]);
const handleClick = () => {
setCount(count + 1);
};
const handleForceUpdate = () => {
// 强制组件重新渲染
forceUpdate();
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Add Count</button>
<button onClick={handleForceUpdate}>Force Update</button>
</div>
);
}
export default MyComponent;
在上面的示例中,我们添加了一个“强制更新”按钮,其中调用了 forceUpdate 方法。当我们单击此按钮时,会强制组件重新渲染,但 count 的值仍然是相同的,因此 useEffect 不会执行。
useReducer举例
假设我们有一个计数器组件,其状态由 count 属性表示,而 dispatch 函数负责更新状态。我们可以使用 useReducer 钩子来管理状态和更新。
现在假设我们传递给 useReducer 的初始状态是 { count: 0 } ,我们在组件中调用了 dispatch({type: 'INCREMENT'}) ,但是我们在处理 INCREMENT 操作时没有改变状态。
这种情况下,useReducer 在更新时会跳过更新,因为状态值没有改变。此时组件不会重新渲染,因为 React 认为应该保持相同的输出。
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state;
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
function handleClick() {
dispatch({ type: 'INCREMENT' });
}
return (
<div>
<p>Count: {state.count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
在这个例子中,我们没有改变状态,所以每次点击“Increment”按钮时,计数器的值不会增加,而且组件也不会重新渲染。
总结
useReducer 是 useState 的替代方案,useState 能做到的事,它都能做到,甚至做得更好。useReducer 某种程度上解耦了操作逻辑(action)和后续的行为(一般是 UI 的更新),虽然代码量变多了,但是看起来更加整洁。
函数式组件的useContext
context的作用
主要用于后代之间传值通信,作用与props类似。避免嵌套props一层一层的传值
import React, { Component,Fragment, useContext } from 'react';
//创建Context实例,类似Ref
const MyContext=React.createContext()
class Dome extends Component {
state = { Name:'jack',age:18 }
render() {
const {Name,age}=this.state
return (
<div className='parent' style={{width:'500px',height:'400px',background:'gray'}}>
<h3>我是Dome组件</h3>
<h4>我的名字是{Name}</h4>
<!--组件外嵌套提供标签,用于广播其中的值Name,B组件下的所有组件包括B组件只要响应了,就都能接到-->
<MyContext.Provider value={{Name,age}}>
<!--下面Name的是通过props传递到B组件的,只有B组件能接到-->
<B Name={Name}/>
</MyContext.Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div className='child' style={{width:'400px',height:'300px',background:'skyblue'}}>
<h3>我是B组件</h3>
<h4>我父亲的名字是{this.props.Name}</h4>
<A/>
</div>
);
}
}
class A extends Component {
//类式组件响应context
static contextType=MyContext
render() {
const {Name,age}=this.context
return (
<div className='grand'style={{width:'300px',height:'200px',background:'orange'}}>
<h3>我是AA组件</h3>
<h4>我爷爷的名字是{Name},年龄{age}</h4>
<AA/>
</div>
);
}
}
function AA () {
//函数式组件响应context
const {Name,age}=useContext(MyContext)
return (
<div className='grand'style={{width:'250px',height:'100px',background:'pink'}}>
<h3>我是AA组件</h3>
<h4>我爷爷的名字是{Name},年龄{age}</h4>
</div>
);
}
export default Dome;
函数式组件的React.memo
React.memo示例
我们先从一个简单的示例入手
以下是一个常规的父子组件关系,打开浏览器控制台并观察,每次点击父组件中的 + 号按钮,都会导致子组件渲染。
const ReactNoMemoDemo = () => {
const [count, setCount] = React.useState(0);
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<Child name="Son" />
</div>
);
};
const Child = (props) => {
console.log('子组件渲染了');
return <p>Child Name: {props.name}</p>;
};
render(
<ReactNoMemoDemo />
);
子组件的 name 参数明明没有被修改,为什么还是重新渲染?
这就是 React 的渲染机制,组件内部的 state 或者 props 一旦发生修改,整个组件树都会被重新渲染一次,即时子组件的参数没有被修改,甚至无状态组件。
如何处理这个问题?接下里就要说到 React.memo
React.memo介绍
React.memo是 React 官方提供的一个高阶组件,用于缓存我们的需要优化的组件
如果你的组件在相同 props 的情况下渲染相同的结果,那么你可以通过将其包装在 React.memo 中调用,以此通过记忆组件渲染结果的方式来提高组件的性能表现。这意味着在这种情况下,React 将跳过渲染组件的操作并直接复用最近一次渲染的结果。
让我们来改进一下上述的代码,只需要使用 React.memo 组件包裹起来即可,其他用法不变
React.memo使用
function ReactMemoDemo() {
const [count, setCount] = React.useState(0);
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<Child name="Son" />
</div>
);
}
const Child = React.memo(props => {
console.log('子组件渲染了');
return <p>Child Name: {props.name}</p>;
});
render(
<ReactMemoDemo />
);
再次观察控制台,应该会发现再点击父组件的按钮,子组件已经不会重新渲染了。
这就是 React.memo 为我们做的缓存优化,渲染 Child 组件之前,对比 props,发现 name 没有发生改变,因此返回了组件上一次的渲染的结果。
React.memo 仅检查 props 变更。如果函数组件被 React.memo 包裹,且其实现中拥有 useState,useReducer 或 useContext 的 Hook,当 state 或 context 发生变化时,它仍会重新渲染。
当然,如果我们子组件有内部状态并且发生了修改,依然会重新渲染(正常行为)。
React.memo的使用场景
看到这里,不禁会产生疑问,既然如此,那我直接为每个组件都添加 React.memo 来进行缓存就好了,再深究一下,为什么 React 不直接默认为每个组件缓存呢?那这样既节省了开发者的代码,又为项目带来了许多性能的优化,这样不好吗?
使用太多的缓存,反而容易带来 负提升。
前面有说到,组件使用缓存策略后,在被更新之前,会比较最新的 props 和上一次的 props 是否发生值修改,既然有比较,那就有计算,如果子组件的参数特别多且复杂繁重,那么这个比较的过程也会十分的消耗性能,甚至高于 虚拟 DOM 的生成,这时的缓存优化,反而产生的负面影响,这个就是关键问题。
当然,这种情况很少,大部分情况还是 组件树的 虚拟 DOM 计算比缓存计算更消耗性能。但是,既然有这种极端问题发生,就应该把选择权交给开发者,让我们自行决定是否需要对该组件进行渲染,这也是 React 不默认为组件设置缓存的原因。
也因此,在 React 社区中,开发者们也一致的认为,不必要的情况下,不需要使用 React.memo。
什么时候该用? 组件渲染过程特别消耗性能,以至于能感觉到到,比如:长列表、图表等
什么时候不该用?组件参数结构十分庞大复杂,比如未知层级的对象,或者列表(城市,用户名)等
React.memo 二次优化
React.memo 默认每次会对复杂的对象做对比,如果你使用了 React.memo 缓存的组件参数十分复杂,且只有参数属性内的某些/某个字段会修改,或者根本不可能发生变化的情况下,你可以再粒度化的控制对比逻辑,React.memo 接受两个参数:组件和一个判断函数(可选)。判断函数用于指定是否应该根据新的 props 进行重新渲染组件。如果不提供判断函数,React.memo 默认使用浅比较来对比新旧 props。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function shouldMemo(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, shouldMemo);
如果对 class 组件有了解过的朋友应该知道,class 组件有一个生命周期叫做 shouldComponentUpdate(),也是通过对比 props 来告诉组件是否需要更新,但是与这个逻辑刚好相反。
总结
对于 React.memo,无需刻意去使用它进行缓存组件,除非你能感觉到你需要。另外,不缓存的组件会多次的触发 render,因此,如果你在组件内有打印信息,可能会被多次的触发,也不用去担心,即使强制被 rerender,因为状态没有发生改变,因此每次 render 返回的值还是一样,所以也不会触发真实 dom 的更新,对页面实际没有任何影响。
函数式组件的useMemo
useMemo示例
同样,我们先看一个例子,calculatedCount 变量是一个假造的比较消耗性能的计算表达式,为了方便显示性能数据打印时间,我们使用了 IIFE 立即执行函数,每次计算 calculatedCount 都会输出它的计算消耗时间。
打开控制台,因为是 IIFE,所以首次会直接打印出时间。然后,再点击 + 号,会发现再次打印出了计算耗时。这是因为 React 组件函数重渲染的时候,不仅是 jsx,而且变量,函数这种也全部都会再次声明一次,因此导致了 calculatedCount 重新执行了初始化(计算),但是这个变量值并没有发生改变,如果每次渲染都要重新计算,那也是十分的消耗性能。
注意观察,在计算期间,页面会发生卡死,不能操作,这是 JS 引擎 的机制,在执行任务的时候,页面永远不会进行渲染,直到任务结束为止。这个过程对用户体验来说是致命的,虽然我们可以通过微任务去处理这个计算过程,从而避免页面的渲染阻塞,但是消耗性能这个问题仍然存在,我们需要通过其他方式去解决。
function UseMemoDemo() {
const [count, setCount] = React.useState(0);
const calculatedCount = (() => {
let res = 0;
const startTime = Date.now();
for (let i = 0; i <= 100000000; i++) {
res++;
}
console.log(`Calculated Count 计算耗时:${Date.now() - startTime} ms`);
return res;
})();
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<div>Calculated Count: {calculatedCount}</div>
</div>
);
}
useMemo介绍
const memoizedValue = useMemo(() => {
// 处理复杂计算,并 return 结果
}, []);
useMemo返回一个缓存过的值,把 "创建" 函数和依赖项数组作为参数传入useMemo,它仅会在某个依赖项改变时才重新计算memoized值。这种优化有助于避免在每次渲染时都进行高开销的计算
第一个参数是函数,函数中需要返回计算值
第二个参数是依赖数组
- 如果不传,则每次都会初始化,缓存失败
- 如果传空数组,则永远都会返回第一次执行的结果,
相当于vue的计算属性Computed - 如果传状态,则在依赖的状态变化时,才会从新计算,如果这个缓存状态依赖了其他状态的话,则需要提供进去。
这下就很好理解了,我们的 calculatedCount 没有任何外部依赖,因此只需要传递空数组作为第二个参数,开始改造
useMemo使用
function UseMemoDemo() {
const [count, setCount] = React.useState(0);
const calculatedCount = useMemo(() => {
let res = 0;
const startTime = Date.now();
for (let i = 0; i <= 100000000; i++) {
res++;
}
console.log(`Memo Calculated Count 计算耗时:${Date.now() - startTime} ms`);
return res;
}, []);
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<div>Memorized Calculated Count: {calculatedCount}</div>
</div>
);
}
现在,"Memo Calculated Count 计算耗时"的输出信息永远只会打印一次,因为它被无限缓存了。
useMemo的使用场景
**何时使用 **
当你的表达式十分复杂需要经过大量计算的时候
下面示例中,我们使用状态提升,将子组件的 click 事件函数放在了父组件中,点击父组件的 + 号,发现子组件被重新渲染
const FunctionPropDemo = () => {
const [count, setCount] = React.useState(0);
const handleChildClick = () => {
//
};
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<Child onClick={handleChildClick} />
</div>
);
};
const Child = React.memo(props => {
console.log('子组件渲染了');
return (
<div>
<div>Child</div>
<button onClick={props.onClick}>Click Me</button>
</div>
);
});
render(<FunctionPropDemo />);
于是我们想到用 memo 函数包裹子组件,给缓存起来
const FunctionPropDemo = () => {
const [count, setCount] = React.useState(0);
const handleChildClick = () => {
//
};
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<Child onClick={handleChildClick} />
</div>
);
};
const Child = React.memo(props => {
console.log('子组件渲染了');
return (
<div>
<div>Child</div>
<button onClick={props.onClick}>Click Me</button>
</div>
);
});
render(<FunctionPropDemo />);
但是意外来了,即使被 memo 包裹的组件,还是被重新渲染了,为什么!
我们来逐一分析
- 首先,点击父组件的
+号,count发生变化,于是父组件开始重渲染 - 内部的未经处理的变量和函数都被重新初始化,
useState不会再初始化了, useEffect 钩子函数重新执行,虚拟 dom 更新 - 执行到
Child组件的时候,Child准备更新,但是因为它是memo缓存组件,于是开始浅比较props参数,到这里为止一切正常 Child组件参数开始逐一比较变更,到了onClick函数,发现值为函数,提供的新值也为函数,但是因为刚刚在父组件内部重渲染时被重新初始化了(生成了新的地址),因为函数是引用类型值,导致引用地址发生改变!比较结果为不相等,React仍会认为它已更改,因此重新发生了渲染。
既然函数重新渲染会被重新初始化生成新的引用地址,因此我们应该避免它重新初始化。这个时候,useMemo 的第二个使用场景就来了
const FunctionPropDemo = () => {
const [count, setCount] = React.useState(0);
const handleChildClick = useMemo(() => {
return () => {
//
};
}, []);
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
<Child onClick={handleChildClick} />
</div>
);
};
const Child = React.memo(props => {
console.log('子组件渲染了');
return (
<div>
<div>Child</div>
<button onClick={props.onClick}>Click Me</button>
</div>
);
});
render(<FunctionPropDemo />);
这里我们将原本的 handleChildClick 函数通过 useMemo 包裹起来了,另外函数永远不会发生改变,因此传递第二参数为空数组,再次尝试点击 + 号,子组件不会被重新渲染了。
对于对象,数组,renderProps(参数为 react 组件) 等参数,都可以使用 useMemo 进行缓存
既然 useMemo 可以缓存变量函数等,那组件其实也是一个函数,能不能被缓存呢?我们试一试
继续使用第一个案例,将 React.memo 移除,使用 useMemo 改造
const ReactNoMemoDemo = () => {
const [count, setCount] = React.useState(0);
const memorizedChild = useMemo(() => <Child name="Son" />, []);
return (
<div>
<div>Parent Count: {count}</div>
<button onClick={() => setCount(count => count + 1)}>+</button>
{memorizedChild}
</div>
);
};
const Child = props => {
console.log('子组件渲染了');
return <p>Child Name: {props.name}</p>;
};
render(<ReactNoMemoDemo />);
尝试点击 + 号,是的,Child 被 useMemo 缓存成功了!
总结
同样的,不是必要的情况下,和 React.memo 一样,不需要特别的使用 useMemo
使用场景
- 表达式有复杂计算且不会频发触发更新
- 引用类型的组件参数,函数,对象,数组等(一般情况下对象和数组都会从
useState初始化,useState不会二次执行,主要是函数参数) react组件的缓存
函数式组件的useCallback
深入理解useMemo与useCallBack的区别
前言:在阅读本文前需了解基本数据类型和引用数据类型的区别。
true === true // true
false === false // true
1 === 1 // true
'a' === 'a' // true
{} === {} // false
[] === [] // false
() => {} === () => {} // false
- React.memo()
- React.useCallback()
- React.useMemo()
*以下所有例子将采用函数式组件进行说明
React.memo()
React中当组件的props或state变化时,会重新渲染视图,但实际开发中并不需要多次的渲染,直接导致的结果是用户的体验感不好。 子组件
function ChildComp () {
console.log('render child-comp ...')
return <div>Child Comp ...</div>
}
父组件
function ParentComp () {
const [ count, setCount ] = useState(0)
const increment = () => setCount(count + 1)
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp />
</div>
);
}
结果:子组件信息被多次打印
将子组件用React.memo()包一层,只有当子组件的props和state变化时,才会重新渲染子组件。
子组件(写法一)
import React, { memo } from 'react'
const ChildComp = memo(function () {
console.log('render child-comp ...')
return <div>Child Comp ...</div>
})
子组件(写法二)
import React, { memo } from 'react'
let ChildComp = function () {
console.log('render child-comp ...')
return <div>Child Comp ...</div>
}
ChildComp = memo(ChildComp)
结果:子组件信息只在在父组件被初次渲染的时候打印了一次
React.useCallback()
父组件
function ParentComp () {
const [ count, setCount ] = useState(0)
const increment = () => setCount(count + 1)
const [ name, setName ] = useState('hi~')
const changeName = (newName) => setName(newName) // 父组件渲染时会创建一个新的函数
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp name={name} onClick={changeName}/>
</div>
);
}
结果:子组件的信息仍然打印了多次
原因:
- 点击按钮导致父组件重新渲染(count值发生了变化),并且会重新创建changeName函数(函数也属于引用数据类型,每次重新创建时地址不同),子组件的属性(函数)发生了变化,因此子组件被重新渲染
解决:修改父组件的changeName方法,用useCallback钩子函数包裹一层
import React, { useCallback } from 'react'
function ParentComp () {
// ...
const [ name, setName ] = useState('hi~')
// 每次父组件渲染,返回的是同一个函数引用
const changeName = useCallback((newName) => setName(newName), [])
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp name={name} onClick={changeName}/>
</div>
);
}
useCallback()起到了缓存的作用,即使父组件渲染了,被useCallback()的函数也不会重新创建
React.useMemo()
useMemo 和 useCallback 十分相似,useCallback 用来缓存函数对象,useMemo 用来缓存函数的执行结果。在组件中,会有一些函数具有十分的复杂的逻辑,执行速度比较慢。闭了避免这些执行速度慢的函数返回执行,可以通过 useMemo 来缓存它们的执行结果,像是这样:
const result = useMemo(()=>{
return 复杂逻辑函数();
},[依赖项])
//举例
const someEle = useMemo(()=>{
return <Some a={a} b={b}/>;
}, [a, b]);
组件也是函数,也可以缓存组件,
useMemo 中的函数会在依赖项发生变化时执行,注意!是执行,这点和 useCallback 不同,useCallback 是创建。执行后返回执行结果,如果依赖项不发生变化,则一直会返回上次的结果,不会再执行函数。这样一来就避免复杂逻辑的重复执行。
父组件传递给子组件的数据不是字符串而是对象,React.Memo是否还有用? 子组件
import React, { memo } from 'react'
const ChildComp = memo(function ({ info, onClick }) {
console.log('render child-comp ...')
return <>
<div>Child Comp ... {info.name}</div>
<button onClick={() => onClick('hello')}>改变 name 值</button>
</>
})
123456789
父组件
import React, { useCallback } from 'react'
function ParentComp () {
// ...
const [ name, setName ] = useState('hi~')
const [ age, setAge ] = useState(20)
const changeName = useCallback((newName) => setName(newName), [])
const info = { name, age } // 复杂数据类型属性
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp info={info} onClick={changeName}/>
</div>
);
}
结果:子组件信息被打印了多次
原因:跟传递函数原因类似,当点击按钮时,父组件会被重新渲染,info会生成一个新的对象(拥有新的地址),进而导致子组件被重新渲染
解决:使用useMemo将该对象包裹住
useMemo有两个参数:
- 第一个参数是函数,返回的对象指向同一个引用,不会创建新的对象
- 第二参数是数组,当数组中的变量发生改变时,第一个参数的函数才会返回一个新的对象
function ParentComp () {
// ....
const [ name, setName ] = useState('hi~')
const [ age, setAge ] = useState(20)
const changeName = useCallback((newName) => setName(newName), [])
const info = useMemo(() => ({ name, age }), [name, age]) // 包一层
return (
<div>
<button onClick={increment}>点击次数:{count}</button>
<ChildComp info={info} onClick={changeName}/>
</div>
);
}
三种的对比总结 --重要
-
上述三种方法都是为了解决组件不必要的重复渲染问题 ,但针对的情况有所不同
-
React.Memo(),前提是子组件没有接收父组件传递的引用类型参数(函数,对象,数组),使用:用React.Memo包裹子组件,
就是让无关的子组件不再重新渲染 -
React.useMemo(),计算属性,缓存计算结果的,使用于组件计算的函数,
让计算依赖值的技术结果不发生变化时,计算函数不再创建,当依赖数组中的值发生变化时,useMemo会重新计算函数的结果,并将结果缓存起来。在组件重新渲染时,如果依赖数组中的值没有变化,则直接返回缓存的结果,避免不必要的计算。 -
React.useCallBack(),当子组件接收来自父组件的函数,使用:将函数用useCallback(函数,[])包裹,它是用来缓存函数,它接收一个函数和一个依赖数组作为参数。如果依赖项(那个函数)不发生变化,则一直会返回上次的结果,不会再执行函数。当依赖数组中的值发生变化时,useCallback会返回一个新的函数。
自定义hook
import { useRef, useState, useEffect} from "react";
function useMousePosition(){
const [x, setX] = useState(0);
const [y, setY] = useState(0);
// 防抖函数
function debounce(func, wait) {
let timeout;
return function() {
const context = this;
const args = arguments;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
function updateMouse(e: MouseEvent) {
setX(e.clientX);
setY(e.clientY);
}
// 防抖后的事件处理函数
const debouncedUpdateMouse = debounce(updateMouse,5); // 1000 毫秒 = 1 秒
// 监听鼠标位子
useEffect(() => {
window.addEventListener('mousemove', debouncedUpdateMouse);
})
return [x, y];
}
function Hello() {
const [x, y] = useMousePosition();
return (
<>
<p>当前鼠标位置:{x},{y}</p>
</>
);
};
export default Hello;
模拟错误边界
在函数式组件中,没有直接的“生命周期方法”或“特殊属性”来实现错误边界,因为错误边界通常是类组件的一个特性。不过可以使用React.useEffect和React.useState来模拟错误边界的行为。下面是一个简单的例子:
import React, { useState, useEffect } from 'react';
const ErrorBoundary = ({ children }) => {
const [hasError, setHasError] = useState(false);
useEffect(() => {
const errorHandler = (error) => {
console.error(error);
setHasError(true);
};
const componentDidCatch = (error, errorInfo) => {
console.error(error, errorInfo);
setHasError(true);
};
// 捕获全局错误
window.addEventListener('error', errorHandler);
// 清理函数
return () => {
window.removeEventListener('error', errorHandler);
};
}, []);
if (hasError) {
// 你可以自定义这里的错误显示
return <div>Something went wrong.</div>;
}
// 使用try/catch包裹子组件,但这并不会捕获所有类型的错误
try {
return children;
} catch (error) {
console.error(error);
return <div>Caught an error in ErrorBoundary.</div>;
}
};
export default ErrorBoundary;
在这个例子中,我们创建了一个ErrorBoundary组件,它尝试捕获全局错误以及直接发生在其内部的错误。然而,请注意,由于JavaScript和React的工作方式,这种方法可能无法捕获所有类型的错误。特别是,它可能无法捕获异步代码中的错误,除非这些错误被明确地传递给错误边界。
Fragment碎片标签和空标签
一般做法div标签
坏处,代码中div会造成多层冗余嵌套,
import React, { useState } from 'react';
function Dome() {
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
return (
<div>
<input type="text" value={input1} />
<input type="text" value={input2} />
</div>
);
}
export default Dome;
Fragment碎片标签
坏处,在引入中多一步引入
好处,显示页面时,源码会自动将 <Fragment>给除掉,能且只能接受的属性 Key
import React, { useState } from 'react';
function Dome() {
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
return (
<Fragment>
<input type="text" value={input1} />
<input type="text" value={input2} />
</Fragment>
);
}
export default Dome;
空标签
坏处,没办法给空标签指定属性,如类名给,key值什么的
好处,简单不用引入,源码会自动将空标签给除掉
import React, { useState } from 'react';
function Dome() {
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
return (
<>
<input type="text" value={input1} />
<input type="text" value={input2} />
</>
);
}
export default Dome;
懒加载与Suspense
懒惰加载的目的是使网页首次加载变快,方法是将代码划分成若干chunk, 用户最不可能访问到的页面优先使用 lazy loading, 根据访问到的路径实时按需加载相应的代码。这样,用户不需要一开始就下载大量的代码,因为其中一些代码可能不需要用到。
导入Suspense
import React, { Suspense } from "react";
React.Suspense 是一个新的 React 组件,它可以阻塞渲染,在某个异步操作结束之前暂停渲染,等待异步操作完成后再恢复渲染。在使用 React.lazy() 函数延迟加载组件时,可以使用 React.Suspense 组件来包装异步组件,以便在组件加载期间显示一个占位符或者 loading 状态,让用户知道组件正在加载中。导入Suspense组件的目的是,提供按需加载时的UI fallback,即过渡画面,因为加载需要时间,不使用 Suspense React 会报错。
修改 import 语句
例如:
import QuoteDetail from "./pages/QuoteDetail";
//改为:
const QuoteDetail = React.lazy(() => import("./pages/QuoteDetail"));
这些这样修改的组件是假定用户访问的可能性不高的页面,用户访问的可能性越低,使用懒惰加载的价值就越大。
Suspense需要fallback 属性
App.js 中,将全部路由在 Suspense 组件中,当异步组件被包装在 React.Suspense 组件中时,如果异步加载的组件还未加载完成,则 React.Suspense 组件会自动渲染 fallback 属性所指定的内容,直到异步组件加载完成后再重新渲染组件。 只需要这3步就可实现懒惰加载,对于小的App来说,使用懒惰加载只是一个可选项,可使用可不使用。但对于大型App,懒惰加载的优势会得到极大的体现。
完整的代码例子如下:
import React, { Suspense } from "react";
import { Route, Switch, Redirect } from "react-router-dom";
import Layout from "./components/layout/Layout"
import LoadingSpinner from "./components/UI/LoadingSpinner";
const NewQuote = React.lazy(() => import("./pages/NewQuote"));
const QuoteDetail = React.lazy(() => import("./pages/QuoteDetail"));
const NotFound = React.lazy(() => import("./pages/NotFound"));
const AllQuotes = React.lazy(() => import("./pages/AllQuotes"));
function App() {
return (
<Layout>
<Suspense
fallback={
<div className="centered">
<LoadingSpinner />
</div>
}
>
<Switch>
<Route path="/" exact>
<Redirect to="/quotes" />
</Route>
<Route path="/quotes" exact>
<AllQuotes />
</Route>
<Route path="/quotes/:quoteId">
<QuoteDetail />
</Route>
<Route path="/new-quote">
<NewQuote />
</Route>
<Route path="*">
<NotFound />
</Route>
</Switch>
</Suspense>
</Layout>
);
}
export default App;
react的Portal门户
在 React 中,Portal 是一种能够将子组件渲染到父组件之外的机制。这对于实现一些 UI 功能非常有用,比如创建模态框、对话框等场景。Portal中文门户的意思,表示创建个试图,将视图渲染在根组件之上
使用 Portal 很简单,只需要在 React 组件中使用 ReactDOM.createPortal 方法将需要渲染的子组件作为第一个参数,将需要渲染到的父组件 DOM 元素作为第二个参数即可。例如:
import React from 'react';
import ReactDOM from 'react-dom';
function Modal () {
render() {
return ReactDOM.createPortal(
<div className="modal">
<div className="modal-content">
<h2>{this.props.title}</h2>
<p>{this.props.content}</p>
<button onClick={this.props.onClose}>Close</button>
</div>
</div>,
document.getElementById('modal-root')
);
}
}
function App () {
state = { showModal: false };
handleShowModal = () => {
setState({ showModal: true });
}
handleHideModal = () => {
setState({ showModal: false });
}
render() {
return (
<div>
<button onClick={this.handleShowModal}>Show Modal</button>
{this.state.showModal && (
<Modal
title="Modal Title"
content="This is the modal content."
onClose={this.handleHideModal}
/>
)}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
在这个例子中,我们将 Modal 组件作为子组件渲染到了 <div id="modal-root"> 元素中,而不是 App 组件中。这样就可以在子组件中创建模态框等 UI 功能了。
不常用的hook
了解即可
UseDebugValue
用来给自定义钩子设置标签,标签会在 React 开发工具中显示,用来调试自定义钩子,不常用。
调用 hook 很多次的时候,需要进行 区分 使用
UseDeferredValue
useDeferredValue 用来设置一个延迟的 state,比如我们创建一个 state,并使用 useDeferredValue 获取延迟值:
useDeferredValue 需要一个state的作为参数,会为该state创建一个延迟值 当设置了延迟值后,每次state修改时都会触发两次重新的渲染 这两次执行对于其他的部分没有区别,但是延迟值两次执行的值是不同的 第一次执行时 延迟值是state的旧值, 第二次执行时,延迟值是state的新值 延迟值,总是会比原版的state,慢一步更新
const [queryStr, setQueryStr] = useState('');
const deferredQueryStr = useDeferredValue(queryStr);
上边的代码中 queryStr 就是一个常规的 state,deferredQueryStr 就是 queryStr 的延迟值。设置延迟值后每次调用 setState 后都会触发两次组件的重新渲染。第一次时,deferredQueryStr 的值是 queryStr 修改前的值(旧值),第二次才是修改后的值(新值)。换句话,延迟值相较于 state 来说总会慢一步更新。
延迟值可以用在这样一个场景,一个 state 需要在多个组件中使用。一个组件的渲染比较快,而另一个组件的渲染比较慢。这样我们可以为该 state 创建一个延迟值,渲染快的组件使用正常的 state 优先显示。渲染慢的组件使用延迟值,慢一步渲染。当然必须结合 React.memo 或 useMemo 才能真正的发挥出它的作用。
UseTransition
当我们在组件中修改 state 时,会遇到复杂一些的 state,当修改这些 state 时,甚至会阻塞到整个应用的运行,为了降低这种 state 的影响,React 为我们提供了 useTransition,通过 useTransition 可以降低 setState 的优先级。
const [isPending, startTransition] = useTransition();
useTransition 会返回一个数组,数组中有两个元素,第一个元素是 isPending,它是一个变量用来记录 transition 是否在执行中。第二个元素是 startTransition,它是一个函数,可以将 setState 在其回调函数中调用,这样 setState 方法会被标记为 transition 并不会立即执行,而是在其他优先级更高的方法执行完毕,才会执行。
除了 useTransition 外,React 还直接为为我们提供了一个 startTransition 函数,在不需要使用 isPending 时,可以直接使用 startTransition 也可以达到相同的效果。
// startTransition 的回调函数中设置setState会其他的setState生效后才执行
startTransition(()=>{
setFilterWord2(e.target.value);
});
UseId
生成唯一 id,使用于需要唯一 id 的场景,但不适用于列表的 key。
react的proxy和跨域问题
前端解决跨域-proxy
前端处理跨域的原理,浏览器为了安全,防止别的攻击,所以有同源策略,所以接收不到跨域的信息。解决方法是在客户端开一个node服务器,客户端向node服务器请求,node服务器向外部服务器请求,node服务器没有同源策略,所以可转接信息
react脚手架
在react脚手架的SRC文件中建立setupProxy文件 react会自动识别(虽然我并没有代理成功,可能在vite中的react有点问题)(好像是因为我没下这个插件。。。)
const {createProxyMiddleware}=require('http-proxy-middleware');
module.exports=function(app){
app.use(
createProxyMiddleware('/api',{
target:'http://localhost:5001/',
changOrigin:true,
pathRewrite:{'^/api':""}
})
)
}
vite中的代理
在vite.config.js中配置
server: {
port: 3000,
proxy: {
// 接口地址代理
'/api': {
target: 'http://localhost:5001', // 接口的域名
secure: false, // 如果是https接口,需要配置这个参数
changeOrigin: true, // 如果接口跨域,需要进行这个参数配置
rewrite: path => path.replace(/^\/api/, '')
}
}
}
后端跨域的处理
中间件的使用
import express from 'express';
const app=express()
app.use( (req,res,next) => {
console.log('有人请求服务器在',Date());
next()
})
//跨域的中间件
app.use( (req,res,next) => {
res.setHeader('Access-Control-Allow-Origin','*')
next()
})
app.get('/students',(req,res) => {
const students=[
{id:'001',name:'tom',age:19},
{id:'002',name:'tony',age:19},
{id:'003',name:'jerry',age:19}
]
res.send(students)
})
app.listen(5001,(err) => {
if(!err)console.log('http://localhost:5000/students已启动',Date());
})
使用cors()包
var express = require('express');
var cors = require('cors');
var app = express();
// 开发环境设置,生产环境需要配置安全的参数
app.use(cors());
app.get('/', function (req, res) {
res.status(200).send('hello itas109');
});
app.listen(8080);
react样式的处理
行内样式和类样式
使用行内样式时,属性 style={} 里面要传一个对象。可以直接写入一个对象,也可以写入一个已经声明好的对象。
使用样式文件时,用 import 导入样式文件,在标签上添加 className 属性,由于 JSX 就是 JavaScript,class 需要写成 className ,class 是 JavaScript 的保留字。 从性能角度来说,CSS 的 class 通常比行内样式更好。
import React, { Component } from 'react';
import "./common.css";
class Box extends Component {
constructor(){
super();
this.state ={
styleObj:{
fontSize:'50px'
}
}
}
render() {
return (
// 使用class,container是在common.css中声明的一个样式
<div className="container">
{/* 行内样式 */}
<h1 style={{'fontSize':'20px'}}>标题1</h1>
<h1 style={this.state.styleObj}>标题2</h1>
</div>
);
}
}
export default Box;
模块化样式
使用CSS Modules:CSS Modules是一种让CSS文件具有局部作用域的技术。通过使用CSS Modules,每个组件的样式都可以被限定在自己的作用域内,从而避免样式冲突。
新建后缀名为.module.css的样式文件,例如新建common.module.css的样式文件内容如下:
.success{color:green;font-size:20px;}
.error{color:red;font-size:20px;}
使用的时候,通过 import 导入样式文件,通过文件名[属性]来使用这个样式。
import React, { Component } from 'react';
import classes from "./common.module.css"
class Box extends Component {
render() {
return (
<div>
<span className={clesses.error}>异常信息</span>
</div>
);
}
}
export default Box;
动态切换样式
通过 classNames 插件来完成,根据不同的条件判断,应用不同的样式。
在终端执行下面的命令,安装 classNames yarn add classnames -S
使用的时候: 1、通过 import 先导入classNames 插件; 2、使用 classNames.bind() 方法建立绑定关系; 3、声明样式对象,属性名为样式名称,值为boolean,true表示应该该样式;false表示不应用该样式; 4、在标签上添加 className 属性,值为该样式对象;
具体使用方式如下:
/* common.css 文件内容 */
.success{color:green;font-size:20px;}
.error{color:red;font-size:20px;}
react 代码:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import classNames from 'classnames/bind';
import styles from './common.css'
let cx = classNames.bind(styles); //建立绑定关系
class Box extends Component {
render() {
let names = cx({ //声明样式对象
success:true,
error:this.props.error //从标签属性中读取传过来的值
})
return (
<div>
<h1 className={names}>这是提示信息</h1>
</div>
);
}
}
ReactDOM.render(
<Box error={false} />,
document.getElementById('root')
);
使用这种方式的好处就是可以动态切换DOM元素中应用的样式。
CSS-in-JS
使用CSS-in-JS库:CSS-in-JS是将CSS代码直接写入JavaScript代码中的一种方案。通过使用CSS-in-JS库,样式可以被组件化,从而避免样式冲突。 在 react 里一切皆组件,所以能不能把 clss 也定义成组件模块呢?当然是可以的。styled-components是针对 React 写的一套 css-in-js 框架,简单来讲就是可以在 js 中写 css。
“CSS-in-JS” 是指一种模式,其中 CSS 由 JavaScript 生成而不是在外部文件中定义。
注意此功能并不是 React 的一部分,而是由第三方库提供。 React 对样式如何定义并没有明确态度;如果存在疑惑,比较好的方式是和平时一样,在一个单独的 *.css 文件定义你的样式,并且通过 className 指定它们。
我们来看如何使用 styled-components 插件。
在终端中执行下面的命令,安装 styled-components 插件
yarn add styled-components -S
新建一个样式文件,注意这个样式文件的后缀是 .js 1、通过 import 导入 styled-components; 2、使用 styled 定义样式内容,可以通过属性传参,也可以使用 sass 语法;
//common.js 样式文件
import styled from 'styled-components'
//这种定义的写法类似于 <div class="Container"></div>
const Container = styled.div`
width:500px;
height:500px;
background:${(props)=>props.color};
font-size:30px;
h1 {
font-size:50px;
}
`
export {
Container
}
在react中使用的时候:
import React, { Component } from 'react';
import {Container} from './common'
class Box extends Component {
render() {
return (
<Container color="red">
<h1> hello world</h1>
这里是内容
</Container>
);
}
}
ReactDOM.render(
<Box />,
document.getElementById('root')
);
最终渲染到页面的结果如下:
这种方式的优点是可以动态传递参数。
使用BEM命名规范
BEM是一种CSS命名规范,通过为CSS类名添加块(block)、元素(element)和修饰符(modifier)的前缀,可以有效地避免样式冲突。
总结
- 行内样式和类样式,通常不推荐将 style 属性作为设置元素样式的主要方式。在多数情况下,应使用 className 属性来引用外部 CSS 样式表中定义的 class。style 在 React 应用中多用于在渲染过程中添加动态计算的样式。
- 模块化样式,样式文件的后缀为.module.css,使用的时候以对象的形式来调用样式。
- 动态切换样式,通过 classNames 插件来完成,通过对样式属性设置 true 或者 false,来设置是否将该样式应用到DOM对象中。
- CSS-in-JS,在js文件中写css,样式文件的后缀名为 .js,这种方式最大的优点就是可以传递参数。
转载自:https://juejin.cn/post/7352770048513409075