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