在探索和学习React的过程中,我逐渐领悟到了前端开发的魅力与挑战。React,作为Facebook推出的开源JavaScript库,以其独特的思维方式和强大的功能,引领着前端开发的潮流。在这篇文章中,我将分享我的React学习心得,总结笔记,以期帮助那些同样在前端领域摸索的开发者们。
<div id="root"></div>;
// 类组件和函数式组件
class App extends React.Component {
// 组件数据
constructor() {
super();
this.state = {
message: "Hello World",
};
// 对需要绑定的方法, 提前绑定好this 或者方法写成箭头函数上下文指向React组件
this.btnClick = this.btnClick.bind(this);
}
// 组件方法(实例方法)
btnClick() {
// 1.将state中message值修改掉 2.自动重新执行render函数函数
this.setState({
message: "Hello React",
});
}
// 渲染内容 render方法
render() {
return (
<div>
<h2>{this.state.message}</h2>
<button onClick={this.btnClick}>修改文本</button>
</div>
);
}
}
// 将组件渲染到界面上
const root = ReactDOM.createRoot(document.querySelector("#root"));
// App根组件
root.render(<App />);
如果上下文中没有定义
this
,那么this
默认会指向全局对象,在浏览器中这个全局对象通常是window
。在 React 的类组件中,果没有显示绑定方法的this
,那么调用方法时this
指向会失去组件实例的上下文,进而变成undefined
,从而导致运行时错误。为了避免这种问题,可以使用
.bind()
或箭头函数显式绑定方法的this
,或者在构造函数中使用箭头函数定义方法,从而确保方法的this
指向是组件实例。
....
// 封装App组件
class App extends React.Component {
constructor() {
super()
this.state = {
data: ["Vue", "React", "Angular"]
}
}
render() {
// 1.对data进行for循环
// const liEls = []
// for (let i = 0; i < this.state.data.length; i++) {
// const item = this.state.movies[i]
// const liEl = <li>{item}</li>
// liEls.push(liEl)
// }
// 2.data数组 => liEls数组
// const liEls = this.state.data.map(movie => <li>{movie}</li>)
return (
<div>
<h2>电影列表</h2>
<ul>
{this.state.movies.map(movie => <li>{movie}</li>)}
</ul>
</div>
)
}
}
......
// jsx语法规则
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
count: 1000,
names: ["紫陌", "张三", "王五"],
aaa: undefined,
bbb: null,
ccc: true,
friend: { name: "zimo" },
firstName: "zimo",
lastName: "紫陌",
age: 20,
list: ["aaa", "bbb", "ccc"],
};
}
render() {
// 解构拿到值
const { message, names, count, aaa, bbb, ccc, friend, firstName, lastName, age } = this.state;
// 对内容进行运算后显示(插入表示)
const ageText = age >= 18 ? "成年人" : "未成年人";
const liEls = this.state.list.map((item) => <li>{item}</li>);
return (
<div>
{/* 1.Number / String / Array直接显示出来*/}
<h2>{message}</h2>
<h2>{count}</h2>
<h2>{names}</h2>
{/* 2.undefined/null/Boolean 默认不展示,三种方法都可以显示出来*/}
<h2>{String(aaa)}</h2>
<h2>{bbb + ""}</h2>
<h2>{ccc.toString()}</h2>
{/* 3.Object类型不能作为子元素展示 */}
<h2>{friend.name}</h2> {/* zimo */}
<h2>{Object.keys(friend)[0]}</h2> {/* name */}
{/* 4.可以插入对应表达式 */}
<h2>{10 + 20}</h2>
<h2>{firstName + "" + lastName}</h2>
{/* 5.插入三元表达式 */}
<h2>{ageText}</h2>
<h2>{age >= 18 ? "成年人" : "未成年人"}</h2>
{/* 6.可以调用方法获取结果 */}
<ul>{liEls}</ul>
<ul>
{this.state.list.map((item) => (
<li>{item}</li>
))}
</ul>
<ul>{this.getList()}</ul>
</div>
);
}
getList() {
const liEls = this.state.list.map((item) => <li>{item}</li>);
return liEls;
}
}
// 1.定义App根组件
class App extends React.Component {
constructor() {
super();
this.state = {
title: "紫陌",
imgURL: "https:*****",
href: "https://*****",
isActive: true,
objStyle: { color: "red", fontSize: "30px" },
};
}
render() {
const { title, imgURL, href, isActive, objStyle } = this.state;
// 需求: isActive: true -> active
// 1.class绑定的写法一: 字符串的拼接
const className = `abc cba ${isActive ? "active" : ""}`;
// 2.class绑定的写法二: 将所有的class放到数组中
const classList = ["abc", "cba"];
if (isActive) classList.push("active");
// 3.class绑定的写法三: 第三方库classnames -> npm install classnames
return (
<div>
{/* 1.基本属性绑定 */}
<h2 title={title}>我是h2元素</h2>
<img src={imgURL} alt="" />
<a href={href}>百度一下</a>
{/* 2.绑定class属性: 最好使用className */}
<h2 className={className}>哈哈哈哈</h2>
<h2 className={classList.join(" ")}>哈哈哈哈</h2>
{/* 3.绑定style属性: 绑定对象类型 */}
<h2 style={{ color: "red", fontSize: "30px" }}>紫陌YYDS</h2>
<h2 style={objStyle}>紫陌</h2>
</div>
);
}
}
jsx 语法规则:
? 1.定义虚拟 DOM 时,不要写引号。
? 2.标签中混入 JS 表达式时要用{}。
? 3.样式的类名指定不要用 class,要用 className。
?
? 5.只有一个根标签
? 6.标签必须闭合
? 7.标签首字母
? (1).若小写字母开头,则将该标签转为 html 中同名元素,若 html 中无该标签对应的同名元素,则报错。
? (2).若大写字母开头,react 就去渲染对应的组件,若组件没有定义,则报错。
class App extends React.Component {
constructor() {
super();
this.state = {
count: 10,
};
this.btn1Click = this.btn1Click.bind(this);
}
btn1Click() {
console.log("btn1", this);
this.setState({ count: this.state.count + 1 });
}
btn2Click = () => {
console.log("btn2", this);
this.setState({ count: this.state.count + 1 });
};
btn2Click = () => {
console.log("btn2", this);
this.setState({ count: this.state.count + 1 });
};
btn3Click() {
console.log("btn2", this);
this.setState({ count: this.state.count + 1 });
}
render() {
const { count } = this.state;
return (
<div>
<h2>当前计数:{count}</h2>
{/* 1.this绑定方式一:bind绑定 */}
<button onClick={this.btn1Click}>按钮1</button>
{/* 2.this.绑定方式二:ES6 class fields */}
<button onClick={this.btn2Click}>按钮2</button>
{/* 3.this绑定方式三:直接传入一个箭头函数(重要) */}
<button onClick={() => console.log("btn3Click")}>按钮3</button>
<button onClick={() => this.btn3Click()}>按钮4</button>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
btnClick(event, name, age) {
console.log("btnClick", event, this);
console.log("name,age", name, age);
}
render() {
return (
<div>
{/* 1.event参数传递 */}
<button onClick={this.btnClick.bind(this)}>按钮1</button>
<button
onClick={(event) => {
this.btnClick(event);
}}
>
按钮2
</button>
{/* 额外参数传递 */}
<button onClick={this.btnClick.bind(this, "zimo", 18)}>按钮3(不推荐)</button>
<button onClick={(event) => this.btnClick(event, "zimo", 18)}>按钮4</button>
</div>
);
}
}
// 1.定义App根组件
class App extends React.Component {
constructor() {
super();
this.state = {
isShow: false,
flag: undefined,
};
}
render() {
const { isShow, flag } = this.state;
// 1.条件判断方式一: 使用if进行条件判断
let showElement = null;
if (isShow) {
showElement = <h2>显示紫陌</h2>;
} else {
showElement = <h1>隐藏紫陌</h1>;
}
return (
<div>
{/* 1.方式一: 根据条件给变量赋值不同的内容 */}
<div>{showElement}</div>
{/* 2.方式二: 三元运算符 */}
<div>{isShow ? <h2>显示</h2> : <h2>隐藏</h2>}</div>
{/* 3.方式三: &&逻辑与运算 */}
{/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
<div> {flag && <h2>{flag}</h2>} </div>
</div>
);
}
}
class App extends React.Component {
constructor() {
super();
this.state = {
students: [
{ id: 111, name: "zimo", score: 199 },
{ id: 112, name: "aniy", score: 98 },
{ id: 113, name: "james", score: 199 },
{ id: 114, name: "curry", score: 188 },
],
};
}
render() {
const { students } = this.state;
/* 第一种分别写 */
//分数大于100学生进行展示
const filterStudent = students.filter((item) => {
return item.score > 100;
});
//分数大于100前两名信息
const sliceStudents = filterStudent.slice(0, 2);
return (
<div>
<h2>列表数据</h2>
<div>
{/* 第二种方式链式调用 */}
{students
.filter((item) => item.score > 100)
.slice(0, 2)
.map((item) => {
return (
<div key={item.id}>
<h2>学号: {item.id}</h2>
<h3>姓名: {item.name}</h3>
<h4>分数:{item.score}</h4>
</div>
);
})}
</div>
</div>
);
}
}
JSX 仅仅只是 React.createElement(component, props, …children) 函数的语法糖。 所有的 jsx 最终都会被转换成 React.createElement 的函数调用。
createElement 需要传递三个参数:
参数一:type
参数二:config
./images
所有 jsx 中的属性都在 config 中以对象的属性和值的形式存储
比如传入 className 作为元素的 class;
参数三:children
babel 转换过来的代码可以直接运行。界面依然是可以正常的渲染
<MyComponent />
会被 React 渲染为自定义组件;
<MyComponent />
均为 React 元素。
// 函数式组件
function App(props) {
// 返回值: 和类组件中render函数返回的是一致
return <h1>Hello zimo</h1>;
}
export default App;
父组件:APP 组件
import React, { Component } from "react";
import Hello from "./Hello";
class App extends Component {
constructor() {
super();
this.state = {
isShow: true,
};
}
isShowFun() {
this.setState({
isShow: !this.state.isShow,
});
}
render() {
const { isShow } = this.state;
return (
<div>
我是父组件
{isShow && <Hello />}
<button onClick={() => this.isShowFun()}>显示隐藏子组件</button>
</div>
);
}
}
export default App;
子组件:Hello
import React, { Component } from "react";
class Hello extends Component {
constructor() {
// 1.构造方法: constructor
console.log("1. Hello constructor");
super();
this.state = {
message: "你好紫陌",
};
}
updateText() {
this.setState({
message: "你好啊,zimo~",
});
}
// 2.执行render函数
render() {
console.log("2. Hello render");
const { message } = this.state;
return (
<div>
<h2>{message}</h2>
<p>{message}这是子组件</p>
<button onClick={() => this.updateText()}>修改文本</button>
</div>
);
}
// 3.组件被渲染到DOM:被挂载到DOM
componentDidMount() {
console.log("Hello组件挂载完成");
}
// 4. 组件的DOM被更新完成:DOM发生更新
componentDidUpdate() {
console.log("Hello组件更新完成");
}
// 5.组件从DOM中卸载掉:从DOM移除掉
componentWillUnmount() {
console.log("Hello组件卸载完成");
}
// 不常用的生命周期函数
shouldComponentUpdate() {
return true; //false 不会更新数据
}
// 等等
}
export default Hello;
运行效果
旧版生命周期函数:
- 初始化阶段: 由 ReactDOM.render()触发—初次渲染 1. constructor() 2. componentWillMount() 3. render() 4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息- 更新阶段: 由组件内部 this.setSate()或父组件 render 触发 1. shouldComponentUpdate() 2. componentWillUpdate() 3. render() =====> 必须使用的一个 4. componentDidUpdate()
- 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发 1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
新版生命周期函数:
- 初始化阶段: 由 ReactDOM.render()触发—初次渲染 1. constructor() 2. getDerivedStateFromProps 3. render() 4. componentDidMount() =====> 常用
一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息- 更新阶段: 由组件内部 this.setSate()或父组件重新 render 触发 1. getDerivedStateFromProps 2. shouldComponentUpdate() 3. render() 4. getSnapshotBeforeUpdate 5. componentDidUpdate()
- 卸载组件: 由 ReactDOM.unmountComponentAtNode()触发 1. componentWillUnmount() =====> 常用
一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
父组件:Main:
export class Main extends Component {
constructor() {
super();
this.state = {
banners: [],
productList: [],
};
}
componentDidMount() {
axios.get("http://123.207.32.32:8000/home/multidata").then((res) => {
const banners = res.data.data.banner.list;
const recommend = res.data.data.recommend.list;
this.setState({
banners,
productList: recommend,
});
});
}
render() {
const { banners, productList } = this.state;
return (
<div className="main">
<div>Main</div>
<MainBanner banners={banners} title="紫陌" />
<MainBanner />
<MainProductList productList={productList} />
</div>
);
}
}
子组件:MainBanner
import React, { Component } from "react";
import PropTypes from "prop-types";
export class MainBanner extends Component {
/* es2022的语法等同于下面 MainBanner.defaultProps */
// static defaultProps = {
// banners:[],
// title:"默认标题"
// }
render() {
// console.log(this.props)
const { title, banners } = this.props;
return (
<div className="banner">
<h2>封装一个轮播图: {title}</h2>
<ul>
{banners?.map((item) => {
return <li key={item.acm}>{item.title}</li>;
})}
</ul>
</div>
);
}
}
//MainBanner传入props类型进行验证
MainBanner.propTypes = {
banners: PropTypes.array,
title: PropTypes.string,
};
//MainBanner传入的props的默认值
MainBanner.defaultProps = {
banners: [],
title: "默认标题",
};
export default MainBanner;
props 类型验证:
禹神案例:
1. props限制案例:
----------------------------------------------------------------------------------------------------------
//创建组件
class Person extends React.Component{
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//对标签属性进行类型、必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
.............
function speak(){
console.log('我说话了');
}
2.props的简写方式也就是利用es2022语法
---------------------------------------------------------------------------------------------------
//创建组件
class Person extends React.Component{
constructor(props){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
// console.log(props);
super(props)
console.log('constructor',this.props);
}
//对标签属性进行类型、必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
static defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
render(){
// console.log(this);
const {name,age,sex} = this.props
//props是只读的
//this.props.name = 'jack' //此行代码会报错,因为props是只读的
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
-----------------------------------------------------------------------------------------------------
3. 函数式组件传递props
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry"/>,document.getElementById('test1'))
案例:
父组件:APP
export class App extends Component {
constructor() {
super();
this.state = {
counter: 100,
};
}
//调用修改变量
changeCounter(count) {
this.setState({
counter: this.state.counter + count,
});
}
render() {
const { counter } = this.state;
return (
<div>
<h2>当前计数为:{counter}</h2>
<AddCounter addClick={(count) => this.changeCounter(count);}/>
</div>
);
}
}
子组件:AddCounter
export class AddCounter extends Component {
addCount(count) {
//拿到父组件定义的函数传变量
this.props.addClick(count);
}
render() {
return (
<div>
<button
onClick={(e) => {
this.addCount(10);
}}
>
点我加10
</button>
</div>
);
}
}
图解:
第一种方式:每个组件都可以获取到 props.children:它包含组件的开始标签和结束标签之间的内容。
children 实现的方案虽然可行,但是有一个弊端:通过索引值获取传入的元素很容易出错,不能精准的获取传入的原生;
父组件:APP
export class App extends Component {
render() {
return (
<div>
{/* 1.使用children实现插槽 */}
<NavBar>
<button>按钮</button>
<h2>zimo</h2>
<span>紫陌</span>
</NavBar>
</div>
);
}
}
子组件:NavBar
export class NavBar extends Component {
render() {
const { children } = this.props;
return (
<div className="nav-bar">
<div className="left">{children[0]}</div>
<div className="center">{children[1]}</div>
<div className="right">{children[2]}</div>
</div>
);
}
}
2.第二种方式:另外一个种方案就是使用 props 实现:通过具体的属性名,可以让我们在传入和获取时更加的精准;
父组件:APP
export class App extends Component {
render() {
const spanEl = <span>紫陌</span>;
return (
<div>
{/* 2.使用props实现插槽 */}
<NavBar leftSlot={<button>按钮2</button>} centerSlot={<h2>zimo</h2>} rightSlot={spanEl} />
</div>
);
}
}
子组件:NavBar
export class NavBar extends Component {
render() {
const { leftSlot, centerSlot, rightSlot } = this.props;
return (
<div className="nav-bar">
<div className="left">{leftSlot}</div>
<div className="center">{centerSlot}</div>
<div className="right">{rightSlot}</div>
</div>
);
}
}
react 作用域插槽效果实现
tabs 案例演示:
React.createContext
创建一个需要共享的 Context 对象:
如果一个组件订阅了 Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的 context 值;
defaultValue 是组件在顶层查找过程中没有找到对应的 Provider,那么就使用默认值
// 1.创建一个Context
const userContext = React.createContext(defaultValue);
Context.Provider
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:
Provider 接收一个 value 属性,传递给消费组件;
一个 Provider 可以和多个消费组件有对应关系;
多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染;
<MyContext.Provider value={/* 某个值*/}
Class.contextType
挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象:
这能让你使用 this.context 来消费最近 Context 上的那个值;
你可以在任何生命周期中访问到它,包括 render 函数中;
//设置组件的contextType为某一个Context
//class.contextType = ThemeContext
HomeInfo.contextType = ThemeContext;
这里,React 组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context。
这里需要 函数作为子元素(function as child)这种做法;
这个函数接收当前的 context 值,返回一个 React 节点;
<UserContext.Consumer>
{(value) => {
return <h2>Info User: {value.nickname}</h2>;
}}
</UserContext.Consumer>
什么时候使用Context.Consumer 呢?
当使用 value 的组件是一个函数式组件时;
当组件中需要使用多个 Context 时;
案例一:
APP 父组件:
theme-context.jsx
--------------------------------------------------------------------------------------------------------
import React from "react"
// 1.创建一个Context
const ThemeContext = React.createContext({ color: "blue", size: 10 })
export default ThemeContext
user-context.jsx
---------------------------------------------------------------------------------------------------------
import React from "react"
// 1.创建一个Context
const UserContext = React.createContext()
export default UserContext
APP.jsx
----------------------------------------------------------------------------------------------------------
import React, { Component } from 'react'
import Home from './Home'
import ThemeContext from "./context/theme-context"
import UserContext from './context/user-context'
import Profile from './Profile'
export class App extends Component {
render() {
return (
<div>
<h2>App</h2>
{/* 通过ThemeContext中Provider中value属性为后代提供数据 */}
<UserContext.Provider value={{nickname: "kobe", age: 30}}>
<ThemeContext.Provider value={{color: "red", size: "30"}}>
<A/>
</ThemeContext.Provider>
</UserContext.Provider>
<Profile/>
</div>
)
}
}
A 组件:
export class Home extends Component {
render() {
return (
<div>
<B />
<C />
</div>
);
}
}
B 组件:类组件
import ThemeContext from "./context/theme-context";
import UserContext from "./context/user-context";
export class B extends Component {
render() {
// 4.第四步操作: 获取数据, 并且使用数据
console.log(this.context);
return (
<div>
{/*获取最近的context */}
<h2>HomeInfo: {this.context.color}</h2>
{/*获取指定的context */}
<UserContext.Consumer>
{(value) => {
return <h2>Info User: {value.nickname}</h2>;
}}
</UserContext.Consumer>
</div>
);
}
}
// 3.第三步操作: 设置组件的contextType为某一个Context
HomeInfo.contextType = ThemeContext;
C 组件:函数组件
import ThemeContext from "./context/theme-context";
function C() {
return (
<div>
{/* 函数式组件中使用Context共享的数据 */}
<ThemeContext.Consumer>
{(value) => {
return <h2> Banner theme:{value.color}</h2>;
}}
</ThemeContext.Consumer>
</div>
);
}
export default HomeBanner;
案例二:
import React, { Component } from "react";
//创建Context对象
const MyContext = React.createContext();
const { Provider, Consumer } = MyContext;
export default class A extends Component {
state = { username: "tom", age: 18 };
render() {
const { username, age } = this.state;
return (
<div className="parent">
<h3>我是A组件</h3>
<h4>我的用户名是:{username}</h4>
<Provider value={{ username, age }}>
<B />
</Provider>
</div>
);
}
}
class B extends Component {
render() {
return (
<div className="child">
<h3>我是B组件</h3>
<C />
</div>
);
}
}
class C extends Component {
//声明接收context
static contextType = MyContext;
render() {
const { username, age } = this.context;
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:{username},年龄是{age}
</h4>
</div>
);
}
}
function C() {
return (
<div className="grand">
<h3>我是C组件</h3>
<h4>
我从A组件接收到的用户名:
<Consumer>{(value) => `${value.username},年龄是${value.age}`}</Consumer>
</h4>
</div>
);
}
在应用开发中一般不用context, 一般都它的封装react插件;
案例一:
export class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello Zimo",
};
}
changeText() {
// 1.基本使用
this.setState({
message: "你好啊, 紫陌",
});
// 2.setState可以传入一个回调函数
// 好处一: 可以在回调函数中编写新的state的逻辑
// 好处二: 当前的回调函数会将之前的state和props传递进来
this.setState((state, props) => {
// 1.编写一些对新的state处理逻辑
// 2.可以获取之前的state和props值
console.log(this.state.message, this.props);
return {
message: "你好啊, 紫陌",
};
});
// 3.setState在React的事件处理中是一个异步调用
// 如果希望在数据更新之后(数据合并), 获取到对应的结果执行一些逻辑代码
// 那么可以在setState中传入第二个参数: callback
this.setState({ message: "你好啊, 紫陌" }, () => {
console.log("++++++:", this.state.message); //后打印,setState异步
});
console.log("------:", this.state.message); //先打印
}
render() {
const { message } = this.state;
return (
<div>
<h2>message: {message}</h2>
<button onClick={(e) => this.changeText()}>修改文本</button>
</div>
);
}
}
案例二:宇哥
import React, { Component } from "react";
export default class Demo extends Component {
state = { count: 0 };
add = () => {
//对象式的setState
//1.获取原来的count值
const { count } = this.state;
//2.更新状态
this.setState({ count: count + 1 }, () => {
console.log(this.state.count);
});
//console.log('12行的输出',this.state.count); //0
//函数式的setState
this.setState((state) => ({ count: state.count + 1 }));
};
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.add}>点我+1</button>
</div>
);
}
}
案例:
import React, { Component } from "react";
function Hello(props) {
return <h2>{props.message}</h2>;
}
export class App extends Component {
constructor(props) {
super(props);
this.state = {
message: "Hello Zimo",
counter: 0,
};
}
changeText() {
this.setState({ message: "你好啊,紫陌" });
console.log(this.state.message);
}
increment() {
console.log("------");
//以下三个最终是counter:1
// this.setState({
// counter: this.state.counter + 1
// })
// this.setState({
// counter: this.state.counter + 1
// })
// this.setState({
// counter: this.state.counter + 1
// })
//以下结果是counter:3
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
// this.setState((state) => {
// return {
// counter: state.counter + 1
// }
// })
}
render() {
const { message, counter } = this.state;
console.log("render被执行");
return (
<div>
<h2>message: {message}</h2>
<button onClick={(e) => this.changeText()}>修改文本</button>
<h2>当前计数: {counter}</h2>
<button onClick={(e) => this.increment()}>counter+1</button>
<Hello message={message} />
</div>
);
}
}
?
setState 默认是异步的
在*React18 之后,默认所有的操作都被放到了批处理中(异步处理)
如果希望代码可以同步会拿到,则需要执行特殊的 flushSync 操作
import { flushSync } from 'react-dom'
changeText() {
setTimeout(() => {
// 在react18之前, setTimeout中setState操作, 是同步操作
// 在react18之后, setTimeout中setState异步操作(批处理)
flushSync(() => {
this.setState({ message: "你好啊, 李银河" })
})
console.log(this.state.message)
}, 0);
}
React 给我们提供了一个生命周期方法 shouldComponentUpdate(很多时候,我们简称为 SCU),这个方法接受参数,并且需要有 返回值:
该方法有两个参数:
参数一:nextProps 修改之后,最新的 props 属性
参数二:nextState 修改之后,最新的 state 属性
该方法返回值是一个 boolean 类型:
返回值为 true,那么就需要调用 render 方法;
返回值为 false,那么久不需要调用 render 方法
默认返回的是 true,也就是只要 state 发生改变,就会调用 render 方法;
案例
APP组件------------------------------------------------------------
import React, { PureComponent } from 'react'
import Home from './Home'
import Recommend from './Recommend'
import Profile from './Profile'
export class App extends PureComponent {
constructor() {
super()
this.state = {
message: "Hello World",
counter: 0
}
}
// shouldComponentUpdate(nextProps, newState) {
// // App进行性能优化的点
// if (this.state.message !== newState.message || this.state.counter !== newState.counter) {
// return true
// }
// return false
// }
changeText() {
this.setState({ message: "你好啊,紫陌!" })
// this.setState({ message: "Hello World" })
}
increment() {
this.setState({ counter: this.state.counter + 1 })
}
render() {
console.log("App render")
const { message, counter } = this.state
return (
<div>
<h2>App-{message}-{counter}</h2>
<button onClick={e => this.changeText()}>修改文本</button>
<button onClick={e => this.increment()}>counter+1</button>
<Home message={message}/>
<Recommend counter={counter}/>
<Profile message={message}/>
</div>
)
}
}
export default App
Home组件----------------------------------------------------------------------------------------
import React, { PureComponent } from 'react'
export class Home extends PureComponent {
constructor(props) {
super(props)
this.state = {
friends: []
}
}
// shouldComponentUpdate(newProps, nextState) {
// // 自己对比state是否发生改变: this.state和nextState
// if (this.props.message !== newProps.message) {
// return true
// }
// return false
// }
render() {
console.log("Home render")
return (
<div>
<h2>Home Page: {this.props.message}</h2>
</div>
)
}
}
export default Home
Profile组件----------------------------------------------------------------------------------------------------
import { memo } from "react"
const Profile = memo(function(props) {
console.log("profile render")
return <h2>Profile: {props.message}</h2>
})
export default Profile
修改 state 数据必须把 state 数据拷贝出来替换掉原来的数据,PureComponent 才可以调用 render 函数。
import React, { PureComponent } from "react";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{ name: "你不知道JS", price: 99, count: 1 },
{ name: "JS高级程序设计", price: 88, count: 1 },
{ name: "React高级设计", price: 78, count: 2 },
{ name: "Vue高级设计", price: 95, count: 3 },
],
friend: {
name: "kobe",
},
message: "Hello World",
};
}
addNewBook() {
const newBook = { name: "Angular高级设计", price: 88, count: 1 };
// 1.直接修改原有的state, 重新设置一遍
// 在PureComponent是不能重新渲染(render)
/* this.state.books.push(newBook)
this.setState({ books: this.state.books }) */
// 2.赋值一份books, 在新的books中修改, 设置新的books保证不是同一份books才会刷新render
const books = [...this.state.books];
books.push(newBook);
this.setState({ books: books });
}
addBookCount(index) {
// this.state.books[index].count++ 这个也不能重新渲染render
const books = [...this.state.books];
books[index].count++;
this.setState({ books: books });
}
render() {
const { books } = this.state;
return (
<div>
<h2>数据列表</h2>
<ul>
{books.map((item, index) => {
return (
<li key={index}>
<span>
name:{item.name}-price:{item.price}-count:{item.count}
</span>
<button onClick={(e) => this.addBookCount(index)}>+1</button>
</li>
);
})}
</ul>
<button onClick={(e) => this.addNewBook()}>添加新书籍</button>
</div>
);
}
}
export default App;
import React, { PureComponent, createRef } from "react";
export class App extends PureComponent {
constructor() {
this.titleRef = createRef();
this.titleEl = null;
}
getNativeDOM() {
// 1.方式一: 在React元素上绑定一个ref字符串
// console.log(this.refs.why)
// 2.方式二: 提前创建好ref对象, createRef(), 将创建出来的对象绑定到元素
// console.log(this.titleRef.current)
// 3.方式三: 传入一个回调函数, 在对应的元素被渲染之后, 回调函数被执行, 并且将元素传入
console.log(this.titleEl);
}
render() {
return (
<div>
<h2 ref="why">Hello 紫陌</h2>
<h2 ref={this.titleRef}>你好zimo</h2>
<h2 ref={(el) => (this.titleEl = el)}>你好啊紫陌</h2>
<button onClick={(e) => this.getNativeDOM()}>获取DOM</button>
</div>
);
}
}
export default App;
import React, { PureComponent, createRef } from "react";
class HelloWorld extends PureComponent {
test() {
console.log("test------");
}
render() {
return <h1>Hello World</h1>;
}
}
export class App extends PureComponent {
constructor() {
super();
this.hwRef = createRef();
}
getComponent() {
console.log(this.hwRef.current);
this.hwRef.current.test();
}
render() {
return (
<div>
<HelloWorld ref={this.hwRef} />
<button onClick={(e) => this.getComponent()}>获取组件实例</button>
</div>
);
}
}
export default App;
import React, { PureComponent, createRef, forwardRef } from "react";
const HelloWorld = forwardRef(function (props, ref) {
return (
<div>
<h1 ref={ref}>Hello World</h1>
<p>哈哈哈</p>
</div>
);
});
export class App extends PureComponent {
constructor() {
super();
this.hwRef = createRef();
}
getComponent() {
console.log(this.hwRef.current);
}
render() {
return (
<div>
<HelloWorld ref={this.hwRef} />
<button onClick={(e) => this.getComponent()}>获取组件实例</button>
</div>
);
}
}
export default App;
表单元素通常自己维护 state,并根据用户输入进 行更新。
在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
我们将两者结合起来,使 React 的 state 成为“唯一数据源”; ? 渲染表单的 React 组件还控制着用户输入过程中表单发生的操作;
被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”;
由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。
类似 vue 的双向绑定
class App extends PureComponent {
constructor(props) {
super();
this.state = {
name: "zimo",
};
}
inputChange(e) {
console.log(e.target.value);
this.setState({
name: e.target.value,
});
}
render() {
const { name } = this.state;
return (
<div>
{/* 受控组件 */}
<input type="text" value={name} onChange={(e) => this.inputChange(e)} />
<h2>{name}</h2>
{/* 非受控组件 */}
<input type="text" />
</div>
);
}
}
各种表单收集数据例子
class App extends PureComponent {
constructor(props) {
super();
this.state = {
username: "",
password: "",
isAGree: false,
hobbies: [
{ value: "sing", text: "唱", isChecked: false },
{ value: "dance", text: "跳", isChecked: false },
{ value: "rap", text: "rap", isChecked: false },
],
fruit: ["orange"],
};
}
handleSubmitClick(event) {
// 1.阻止默认行为
event.preventDefault();
// 2.获取到所有的表单数据, 对数据进行组织
console.log("获取所有的输入内容");
console.log(this.state.username, this.state.password);
const hobbies = this.state.hobbies.filter((item) => item.isChecked).map((item) => item.value);
console.log("获取爱好: ", hobbies);
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
handleInputChage(event) {
console.log(event);
this.setState({
[event.target.name]: event.target.value,
});
}
handleAgreeChange(event) {
this.setState({
isAGree: event.target.checked,
});
}
handleHobbiesChange(evnet, index) {
const hobbies = [...this.state.hobbies];
hobbies[index].isChecked = evnet.target.checked;
this.setState({ hobbies });
}
handleFruitChange(event) {
const options = Array.from(event.target.selectedOptions);
const values = options.map((item) => item.value);
this.setState({ fruit: values });
// 效果同上
const values2 = Array.from(event.target.selectedOptions, (item) => item.value);
console.log(values2);
}
render() {
const { username, password, isAGree, hobbies, fruit } = this.state;
return (
<div>
{/* 表单 */}
<form onSubmit={(e) => this.handleSubmitClick(e)}>
{/* 1.用户名密码 */}
<div>
<label htmlFor="username">
用户名:
<input
type="text"
id="username"
value={username}
name="username"
onChange={(e) => this.handleInputChage(e)}
/>
</label>
<label htmlFor="password">
密码:
<input
type="password"
id="password"
value={password}
name="password"
onChange={(e) => this.handleInputChage(e)}
/>
</label>
</div>
{/* 2.checkbox 单选*/}
<label htmlFor="agree">
<input type="checkbox" id="agree" checked={isAGree} onChange={(e) => this.handleAgreeChange(e)} />
同意协议
</label>
{/* 3.checkbox */}
<div>
你的爱好:
{hobbies.map((item, index) => {
return (
<label htmlFor={item.value} key={item.value}>
<input
type="checkbox"
value={item.value}
checked={item.isChecked}
id={item.value}
onChange={(e) => this.handleHobbiesChange(e, index)}
/>
<span>{item.text}</span>
</label>
);
})}
</div>
{/* 4.select 多选*/}
<select value={fruit} onChange={(e) => this.handleFruitChange(e)} multiple>
<option value="apple">苹果</option>
<option value="orange">橘子</option>
<option value="banana">香蕉</option>
</select>
{/* 提交按钮 */}
<button type="submit">注册</button>
</form>
</div>
);
}
}
export default App;
import React, { PureComponent, createRef } from "react";
class App extends PureComponent {
constructor(props) {
super();
this.state = {
intro: "紫陌yyds",
};
this.introRef = createRef();
}
handleSubmitClick(event) {
// 1.阻止默认行为
event.preventDefault();
// 2.获取到所有的表单数据, 对数据进行组织
console.log("ref", this.introRef.current.value);
// 3.以网络请求的方式, 将数据传递给服务器(ajax/fetch/axios)
}
render() {
const { intro } = this.state;
return (
<div>
{/* 非受控组件 */}
<input type="text" defaultValue={intro} ref={this.introRef} />
{/* 提交按钮 */}
<button type="submit">注册</button>
</div>
);
}
}
**高阶组件 本身不是一个组件,而是一个函数,这个函数的参数是一个组件,返回值也是一个组件 **
import React, { PureComponent } from "react";
// 定义一个高阶组件
function hoc(Cpn) {
// 定义类组件
class NewCpn extends PureComponent {
render() {
return <Cpn name="zimo" />;
}
}
return NewCpn;
}
class Hello extends PureComponent {
render() {
return <div>你好啊,紫陌</div>;
}
}
const HelloHOC = hoc(Hello);
export class App extends PureComponent {
render() {
return (
<div>
<HelloHOC />
</div>
);
}
}
export default App;
APP 组件
import React, { PureComponent } from "react";
import enhancedUserInfo from "./hoc/enhanced_props";
import About from "./pages/About";
const Home = enhancedUserInfo(function (props) {
return (
<h1>
Home: {props.name}-{props.level}-{props.banners}
</h1>
);
});
const Profile = enhancedUserInfo(function (props) {
return (
<h1>
Profile: {props.name}-{props.level}
</h1>
);
});
const HelloFriend = enhancedUserInfo(function (props) {
return (
<h1>
HelloFriend: {props.name}-{props.level}
</h1>
);
});
export class App extends PureComponent {
render() {
return (
<div>
<Home banners={["轮播图1", "轮播图2"]} />
<Profile />
<HelloFriend />
<About />
</div>
);
}
}
export default App;
enhancedUserInfo 高阶组件:
import { PureComponent } from "react";
// 定义组件: 给一些需要特殊数据的组件, 注入props
function enhancedUserInfo(OriginComponent) {
class NewComponent extends PureComponent {
constructor(props) {
super(props);
this.state = {
userInfo: {
name: "zimo",
level: 99,
},
};
}
render() {
return <OriginComponent {...this.props} {...this.state.userInfo} />;
}
}
return NewComponent;
}
export default enhancedUserInfo;
About 组件
import React, { PureComponent } from "react";
import enhancedUserInfo from "../hoc/enhanced_props";
export class About extends PureComponent {
render() {
return <div>About: {this.props.name}</div>;
}
}
export default enhancedUserInfo(About);
定义 context theme_context.js
import { createContext } from "react";
const ThemeContext = createContext();
export default ThemeContext;
定义 HOC 函数 with_theme.js
import ThemeContext from "../context/theme_context";
function withTheme(OriginComponent) {
return (props) => {
return (
<ThemeContext.Consumer>
{(value) => {
return <OriginComponent {...value} {...props} />;
}}
</ThemeContext.Consumer>
);
};
}
export default withTheme;
Product 组件:
import React, { PureComponent } from "react";
import withTheme from "../hoc/with_theme";
export class Product extends PureComponent {
render() {
const { color, size } = this.props;
return (
<div>
<h2>
Product:{color} - {size}
</h2>
</div>
);
}
}
export default withTheme(Product);
APP 组件:
import React, { PureComponent } from "react";
import ThemeContext from "./context/theme_context";
import Product from "./pages/Product";
export class App extends PureComponent {
render() {
return (
<div>
<ThemeContext.Provider value={{ color: "red", size: 50 }}>
<Product />
</ThemeContext.Provider>
</div>
);
}
}
export default App;
function loginAuth(OriginComponent) {
return (props) => {
// 从localStorage中获取token
const token = localStorage.getItem("token");
if (token) {
return <OriginComponent {...props} />;
} else {
return <h2>请先登录, 再进行跳转到对应的页面中</h2>;
}
};
}
export default loginAuth;
可以利用高阶函数来劫持生命周期,在生命周期中完成自己的逻辑:
下面是高阶组件是计算渲染时间:
import { PureComponent } from "react";
function logRenderTime(OriginComponent) {
return class extends PureComponent {
UNSAFE_componentWillMount() {
this.beginTime = new Date().getTime();
}
componentDidMount() {
this.endTime = new Date().getTime();
const interval = this.endTime - this.beginTime;
console.log(`当前${OriginComponent.name}页面花费了${interval}ms渲染完成!`);
}
render() {
return <OriginComponent {...this.props} />;
}
};
}
export default logRenderTime;
createPortal(
App H2
, document.querySelector(“#zimo”))
index.html-------------------------------------------------------------------------------------------------
<div id="root"></div>
<div id="zimo"></div>
App组件:---------------------------------------------------------------------------------------------------
import React, { PureComponent } from 'react'
import { createPortal } from "react-dom"
export class App extends PureComponent {
render() {
return (
<div className='app'>
<h1>App H1</h1>
{/*元素放到zimo容器 */}
{
createPortal(<h2>App H2</h2>, document.querySelector("#zimo"))
}
</div>
)
}
}
export default App
import React, { PureComponent, Fragment } from 'react'
export class App extends PureComponent {
constructor() {
super()
this.state = {
sections: [
{ title: "哈哈哈", content: "我是内容, 哈哈哈" },
{ title: "呵呵呵", content: "我是内容, 呵呵呵" },
{ title: "嘿嘿嘿", content: "我是内容, 嘿嘿嘿" },
{ title: "嘻嘻嘻", content: "我是内容, 嘻嘻嘻" },
]
}
}
render() {
const { sections } = this.state
return (
<>
<h2>我是App的标题</h2>
<p>我是App的内容, 哈哈哈哈</p>
<hr />
{
sections.map(item => {
return (
{/*这里不能使用简写 因为key*/}
<Fragment key={item.title}>
<h2>{item.title}</h2>
<p>{item.content}</p>
</Fragment>
)
})
}
</>
)
}
}
export default App
例子:
- 可以为应用程序的任何部分启用严格模式
- 不会对 Profile 组件运行严格模式检查;
- 但是,Home 以及它们的所有后代元素都将进行检查;
import React, { PureComponent, StrictMode } from "react";
import Home from "./pages/Home";
import Profile from "./pages/Profile";
export class App extends PureComponent {
render() {
return (
<div>
<StrictMode>
<Home />
</StrictMode>
<Profile />
</div>
);
}
}
export default App;
npm
npm install react-transition-group --save
yarn
yarn add react-transition-group
APP 组件:
import React, { PureComponent } from "react";
import { CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isShow: true,
};
}
render() {
const { isShow } = this.state;
return (
<div>
<button onClick={(e) => this.setState({ isShow: !isShow })}>切换</button>
{/* 无动画 */}
{/* {isShow && <h2>紫陌YYDS</h2>} */}
{/* 有动画 */}
<CSSTransition
// 以下为属性
in={isShow}
unmountOnExit={true}
timeout={2000}
classNames={"zimo"}
appear
// 以下为钩子函数
onEnter={(e) => console.log("开始进入动画")}
onEntering={(e) => console.log("执行进入动画")}
onEntered={(e) => console.log("执行进入结束")}
onExit={(e) => console.log("开始离开动画")}
onExiting={(e) => console.log("执行离开动画")}
onExited={(e) => console.log("执行离开结束")}
>
<div className="section">
<h2>紫陌YYDS</h2>
<p>zimo学前端</p>
</div>
</CSSTransition>
</div>
);
}
}
export default App;
CSS:
.zimo-appear,
.zimo-enter {
opacity: 0;
}
.zimo-appear-active,
.zimo-enter-active {
opacity: 1;
transition: opacity 2s ease;
}
/* 离开动画 */
.zimo-exit {
opacity: 1;
}
.zimo-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
SwitchTransition 可以完成两个组件之间切换的炫酷动画:
SwitchTransition 中主要有一个属性:mode,有两个值
in-out:表示新组件先进入,旧组件再移除;
out-in:表示就组件先移除,新组建再进入;
如何使用 SwitchTransition 呢?
SwitchTransition 组件里面要有 CSSTransition 或者 Transition 组件,不能直接包裹你想要切换的组件;
SwitchTransition 里面的 CSSTransition 或 Transition 组件不再像以前那样接受 in 属性来判断元素是何种状态,取而代之的是 key 属性;
当 key 值改变时,CSSTransition 组件会重新渲染,也就会触发动画
APP 组件:
import React, { PureComponent } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isLogin: true,
};
}
render() {
const { isLogin } = this.state;
return (
<div>
<SwitchTransition mode="out-in">
<CSSTransition
// 当key值改变时,CSSTransition组件会重新渲染,也就会触发动画
key={isLogin ? "login" : "exit"}
classNames={"login"}
timeout={1000}
>
<button onClick={(e) => this.setState({ isLogin: !isLogin })}>{isLogin ? "退出" : "登录"}</button>
</CSSTransition>
</SwitchTransition>
</div>
);
}
}
export default App;
CSS :
.login-enter {
transform: translateX(100px);
opacity: 0;
}
.login-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.login-exit {
transform: translateX(0);
opacity: 1;
}
.login-exit-active {
transform: translateX(-100px);
opacity: 0;
transition: all 1s ease;
}
当我们有一组动画时,需要将这些 CSSTransition 放入到一个 TransitionGroup 中来完成动画
APP 组件:
import React, { PureComponent } from "react";
import { TransitionGroup, CSSTransition } from "react-transition-group";
import "./style.css";
export class App extends PureComponent {
constructor() {
super();
this.state = {
books: [
{ id: 111, name: "你不知道JS", price: 99 },
{ id: 222, name: "JS高级程序设计", price: 88 },
{ id: 333, name: "Vuejs高级设计", price: 77 },
],
};
}
addNewBook() {
const books = [...this.state.books];
books.push({
id: new Date().getTime(),
name: "React高级程序设计",
price: 99,
});
this.setState({ books });
}
removeBook(index) {
const books = [...this.state.books];
books.splice(index, 1);
this.setState({ books });
}
render() {
const { books } = this.state;
return (
<div>
<h2>书籍列表:</h2>
<TransitionGroup component="ul">
{books.map((item, index) => {
return (
<CSSTransition key={item.id} classNames="book" timeout={1000}>
<li>
<span>{item.name}</span>
<button onClick={(e) => this.removeBook(index)}>删除</button>
</li>
</CSSTransition>
);
})}
</TransitionGroup>
<button onClick={(e) => this.addNewBook()}>添加新书籍</button>
</div>
);
}
}
export default App;
CSS:
.book-enter {
transform: translateX(150px);
opacity: 0;
}
.book-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.book-exit {
transform: translateX(0);
opacity: 1;
}
.book-exit-active {
transform: translateX(150px);
opacity: 0;
transition: all 1s ease;
}
export class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
size: 12,
};
}
addTitleSize() {
this.setState({
size: this.state.size + 2,
});
}
render() {
const { size } = this.state;
return (
<div>
<button onClick={(e) => this.addTitleSize()}>字体变大</button>
<h2 style={{ fontSize: `${size}px` }}>我是紫陌</h2>
<h2 style={{ color: "yellowGreen" }}>我是紫陌</h2>
</div>
);
}
}
Home.module.css
.section {
border: 1px solid skyblue;
}
.title {
color: purple;
}
Home.jsx
import React, { PureComponent } from "react";
import homeStyle from "./Home.module.css";
export class Home extends PureComponent {
render() {
return (
<div className={homeStyle.section}>
<div className={homeStyle.title}>Home的标题</div>
</div>
);
}
}
export default Home;
官方文档也有提到过 CSS in JS 这种方案:
在传统的前端开发中,我们通常会将结构(HTML)、样式(CSS)、逻辑(JavaScript)进行分离。
认识 styled-components
目前比较流行的 CSS-in-JS 的库
目前可以说 styled-components 依然是社区最流行的 CSS-in-JS 库
props、attrs 属性
props 可以被传递给 styled 组件 ? 获取 props 需要通过${}传入一个插值函数,props 会作为该函数的参数;
这种方式可以有效的解决动态样式的问题;
添加 attrs 属性
export const SectionWrapper = styled.div.attrs((props) => ({
// 可以通过attrs给标签模板字符串中提供属性
tColor: props.color || "blue",
}))` xxx xxx `;
案例
APP 组件:
import React, { PureComponent } from "react";
import { AppWrapepr, SectionWrapper } from "./style";
export class App extends PureComponent {
constructor() {
super();
this.state = {
size: 30,
color: "yellow",
};
}
render() {
const { size, color } = this.state;
return (
<AppWrapepr>
<SectionWrapper size={size} color={color}>
<h2 className="title">我是紫陌</h2>
<p className="content">我是zimo</p>
<button onClick={(e) => this.setState({ color: "skyblue" })}>修改颜色</button>
</SectionWrapper>
<div className="footer">
<h2>这是底部</h2>
<p>zimo@yyds</p>
</div>
</AppWrapepr>
);
}
}
export default App;
?
style.js 文件
import styled from "styled-components";
import { primaryColor, largeSize } from "./style/variables";
// 基本使用
export const AppWrapepr = styled.div`
.footer {
border: solid 1px blue;
}
`;
// 子元素单独抽取到一个样式组件
export const SectionWrapper = styled.div.attrs((props) => ({
// 可以通过attrs给标签模板字符串中提供属性
tColor: props.color || "blue",
}))`
border: 1px solid red;
.title {
/* 可以接受外部传入的props */
font-size: ${(props) => props.size}px;
color: ${(props) => props.tColor};
&:hover {
background-color: purple;
}
}
/* 从一个单独文件中引入变量 */
.content {
font-size: ${largeSize}px;
color: ${primaryColor};
}
`;
variables.js 文件 (变量文件)
export const primaryColor = "#ff8822";
export const secondColor = "#ff7788";
export const smallSize = "12px";
export const middleSize = "14px";
export const largeSize = "18px";
styled 设置主题,支持样式的继承
index.jsx(根组件传主题变量)
<ThemeProvider theme={{ color: "purple", size: "50px" }}>
<App />
</ThemeProvider>
APP.jsx
import React, { PureComponent } from "react";
import { HomeWrapper, ButtonWrapper } from "./style";
export class Home extends PureComponent {
render() {
return (
<HomeWrapper>
<h2 className="header">商品列表</h2>
<ButtonWrapper>哈哈哈</ButtonWrapper>
</HomeWrapper>
);
}
}
export default Home;
style.js
import styled from "styled-components";
const Button = styled.button`
border: 1px solid red;
border-radius: 5px;
`;
// 继承
export const ButtonWrapper = styled(Button)`
background-color: #0f0;
color: #fff;
`;
// 主题
export const HomeWrapper = styled.div`
.header {
color: ${(props) => props.theme.color};
font-size: ${(props) => props.theme.size};
}
`;
React 在 JSX 给了我们开发者足够多的灵活性,你可以像编写 JavaScript 代码一样,通过一些逻辑来决定是否添加某些 class
用于动态添加 classnames 的一个库
yarn add classnames
import React, { PureComponent } from "react";
import classNames from "classnames";
export class App extends PureComponent {
constructor() {
super();
this.state = {
isbbb: true,
isccc: true,
};
}
render() {
const { isbbb, isccc } = this.state;
const classList = ["aaa"];
if (isbbb) classList.push("bbb");
if (isccc) classList.push("ccc");
const classname = classList.join(" ");
return (
<div>
{/* 不推荐 */}
<h2 className={`aaa ${isbbb ? "bbb" : ""} ${isccc ? "ccc" : ""}`}>我是紫陌</h2>
{/* 不推荐 */}
<h2 className={classname}>我是紫陌</h2>
{/* 推荐 第三方库 */}
<h2 className={classNames("aaa", { bbb: isbbb, ccc: isccc })}>哈哈哈哈</h2>
<h2 className={classNames(["aaa", { bbb: isbbb, ccc: isccc }])}>呜呜呜</h2>
</div>
);
}
}
语法:
classNames(' foo','bar'); //=>'foo bar'
classNames('foo',{bar: true });//=>·'foo bar'
classNames({'foo-bar': false });//=>''
classNames({ foo: true },{ bar: true });//=>'foo bar'
classNames({ foo: true, bar: true }); //=>.'foo bar'
classNames('foo', { bar: true, duck: false }, 'baz', { quux: true }); //=>'foo bar baz quux'
classNames(null,false,'bar',undefined,0,1,{baz:null}, '') // 'bar 1'
函数式编程中有一个非常重要的概念叫纯函数,JavaScript 符合函数式编程的范式,所以也有纯函数的概念;
在 react 开发中纯函数是被多次提及的,比如 react 中组件就被要求像是一个纯函数(为什么是像,因为还有 class 组件),redux 中有一个 reducer 的概念,也是要求 必须是一个纯函数;
简单概要纯函数:
纯函数副作用概念:
纯函数的作用和优势
redux 和 react 没有直接的关系,完全可以在 React, Angular, Ember, jQuery, or vanilla JavaScript 中使用 Redux
store/index.js
const { createStore } = require("redux");
const reducer = require("./reducer.js");
// 创建的store
const store = createStore(reducer);
module.exports = store;
reducer.js
const { ADD_NUMBER, CHANGE_NAME } = require("./constants");
// 初始化数据
const initialState = {
name: "张三",
counter: 100,
};
function reducer(state = initialState, action) {
switch (action.type) {
case CHANGE_NAME:
return { ...state, name: action.name };
case ADD_NUMBER:
return { ...state, counter: state.counter + action.num };
}
}
module.exports = reducer;
constants.js
const ADD_NUMBER = "add_number";
const CHANGE_NAME = "change_name";
module.exports = {
ADD_NUMBER,
CHANGE_NAME,
};
actionCreators.js
const { CHANGE_NAME, ADD_NUMBER } = require("./constants.js");
const changeNameAction = (name) => ({
type: CHANGE_NAME,
name,
});
const addNumberAction = (num) => ({
type: ADD_NUMBER,
num,
});
module.exports = {
changeNameAction,
addNumberAction,
};
上面四模块就是 store 的结构
组件派发 Action
const store = require("./store1/index.js");
const { addNumberAction, changeNameAction } = require("./actionCreator.js");
const unsubscribe = store.subscribe(() => {
console.log("订阅数据的变化:", store.getSatte());
});
store.dispatch(changeNameAction("zimo"));
store.dispatch(changeNameAction("紫陌"));
store.dispatch(changeNameAction("yy"));
store.dispatch(addNumberAction(13));
store.dispatch(addNumberAction(167));
store.dispatch(addNumberAction(137));
Store 目录结构代码略过。。。。同上。直接看组件
APP 组件:
import React, { Component } from "react";
import Home from "./pages/home";
import Profile from "./pages/profile";
import "./style.css";
import store from "./store";
class App extends Component {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({ counter: state.counter });
});
}
render() {
const { counter } = this.state;
return (
<div>
<h2>App counter: {counter}</h2>
<div className="pages">
<Home />
<Profile />
</div>
</div>
);
}
}
export default App;
Home 组件:
import React, { PureComponent } from "react";
import store from "../store";
import { addNumberAction } from "../store/actionCreators";
export class Home extends PureComponent {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({ counter: state.counter });
});
}
addNumber(num) {
store.dispatch(addNumberAction(num));
}
render() {
const { counter } = this.state;
return (
<div>
<h2>Home counterer: {counter}</h2>
<div>
<button onClick={(e) => this.addNumber(1)}>+1</button>
<button onClick={(e) => this.addNumber(5)}>+5</button>
<button onClick={(e) => this.addNumber(8)}>+8</button>
</div>
</div>
);
}
}
export default Home;
Profile 组件
import React, { PureComponent } from "react";
import store from "../store";
import { addNumberAction } from "../store/actionCreators";
export class profile extends PureComponent {
constructor() {
super();
this.state = {
counter: store.getState().counter,
};
}
componentDidMount() {
store.subscribe(() => {
const state = store.getState();
this.setState({ counter: state.counter });
});
}
addNumber(num) {
store.dispatch(addNumberAction(num));
}
render() {
const { counter } = this.state;
return (
<div>
<h2>Profile Counter: {counter}</h2>
<div>
<button onClick={(e) => this.addNumber(1)}>+1</button>
<button onClick={(e) => this.addNumber(5)}>+5</button>
<button onClick={(e) => this.addNumber(8)}>+8</button>
</div>
</div>
);
}
}
export default profile;
上面就是基本使用。但是有个问题。每次写一个组件就要写一个一套相似的逻辑如图:
connect 高阶函数 实际上 redux 官方帮助我们提供了 react-redux 的库,可以直接在项目中使用,并且实现的逻辑会更加的严谨和高效。
安装 react-redux:yarn add react-redux
在 index.js 文件中:context 的全局注入一个 store
import App from "./App";
import { Provider } from "react-redux";
import store from "./store";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<App />
</Provider>
);
About 组件:有加减逻辑
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { addNumberAction, subNumberAction } from "../store/actionCreators";
export class About extends PureComponent {
calcNumber(num, isAdd) {
if (isAdd) {
console.log("加", num);
this.props.addNumber(num);
} else {
console.log("减", num);
this.props.subNumber(num);
}
}
render() {
const { counter } = this.props;
return (
<div>
<h2>About Page: {counter}</h2>
<div>
<button onClick={(e) => this.calcNumber(6, true)}>+6</button>
<button onClick={(e) => this.calcNumber(88, true)}>+88</button>
<button onClick={(e) => this.calcNumber(6, false)}>-6</button>
<button onClick={(e) => this.calcNumber(88, false)}>-88</button>
</div>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addNumber: (num) => {
dispatch(addNumberAction(num));
},
subNumber(num) {
dispatch(subNumberAction(num));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
- connect()函数是一个高阶函数接收两个参数,connect 函数返回一个高阶组件。最后一个括号是放组件的
- 使用了 connect 函数。mapStateToProps 把 store 上的数据映射到了 props。组件通过 props 拿到。action 也是如此。类似 vue 中的 mapState 辅助函数,mapAction 辅助函数
redux-thunk 是可以发送异步的请求**,默认情况下的 dispatch(action),action 需要是一个 JavaScript 的对象**;
redux-thunk 可以让 dispatch(action 函数),action 可以是一个函数;
该函数会被调用,并且会传给这个函数一个 dispatch 函数和 getState 函数;
dispatch 函数用于我们之后再次派发 action;
getState 函数考虑到我们之后的一些操作需要依赖原来的状态,用于让我们可以获取之前的一些状态;
使用 redux-thunk
yarn add redux-thunk
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer.js";
const store = createStore(reducer, applyMiddleware(thunk));
export default store;
**案例代码:异步 action:**组件中通过 props 调用 action 发情网络请求
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { fetchHomeMultidataAction } from "../store/actionCreators";
export class Category extends PureComponent {
componentDidMount() {
this.props.fetchHomeMultidata();
}
render() {
return <h2>Category Page: {this.props.counter}</h2>;
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchHomeMultidata: () => {
dispatch(fetchHomeMultidataAction());
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Category);
actionCreators.js 中:
export const changeBannersAction = (banners) => ({
type: actionTypes.CHANGE_BANNERS,
banners,
});
export const changeRecommendsAction = (recommends) => ({
type: actionTypes.CHANGE_RECOMMENDS,
recommends,
});
export const fetchHomeMultidataAction = () => {
// 普通action
/*
如果是一个普通action,那么我们这里需要返回一个action对象
问题:对象是不能直接拿到服务器请求的异步数据的
return {}
*/
// 异步action处理返回函数
/*
如果返回一个函数那么redux是不支持的。需要借助插件redux-thunk
*/
return (dispatch, getState) => {
//异步操作
axios.get("http://xxxxxx/home/multidata").then((res) => {
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
// 派发action
dispatch(changeBannersAction(banners));
dispatch(changeRecommendsAction(recommends));
});
};
};
组件中 mapStateToProps 函数直接获取即可。
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer.js";
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
**combineReducers****函数实现模块化**
store/index.js
import { createStore, applyMiddleware, compose, combineReducers } from "redux";
import thunk from "redux-thunk";
import counterReducer from "./counter";
import homeReducer from "./home";
// 将两个reducer合并在一起
const reducer = combineReducers({
counter: counterReducer,
home: homeReducer,
});
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducer, composeEnhancers(applyMiddleware(thunk)));
export default store;
combineReducers实现原理
import counterReducer from "./counter";
import homeReducer from "./home";
// combineReducers实现原理(了解)
function reducer(state = {}, action) {
// 返回一个对象, store的state
return {
//state.counter 第一次undefined 就拿到默认值
counter: counterReducer(state.counter, action),
home: homeReducer(state.home, action),
};
}
安装 Redux Toolkit:
npm install @reduxjs/toolkit react-redux
store 的创建
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./features/counter";
import homeReducer from "./features/home";
const store = configureStore({
reducer: {
counter: counterReducer,
home: homeReducer,
},
});
export default store;
通过 createSlice 创建一个 slice。
store/features/counter.js(counter 的 reducer)
import { createSlice } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
counter: 999,
},
reducers: {
addNumber(state, { payload }) {
console.log(payload);
state.counter = state.counter + payload;
},
subNumber(state, { payload }) {
state.counter = state.counter - payload;
},
},
});
export const { addNumber, subNumber } = counterSlice.actions;
export default counterSlice.reducer;
组件中展示数据:(这部分热 redux 一样)
import React, { PureComponent } from "react";
import { connect } from "react-redux";
import { addNumber } from "../store/features/counter";
export class About extends PureComponent {
render() {
const { counter } = this.props;
return (
<div>
<h2>About Page: {counter}</h2>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
counter: state.counter.counter,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addNumber: (num) => {
dispatch(addNumber(num));
},
};
};
export default connect(mapStateToProps, mapDispatchToProps)(About);
当 createAsyncThunk 创建出来的 action 被 dispatch 时,会存在三种状态:
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import axios from "axios";
// 1.
export const fetchHomeMultidataAction = createAsyncThunk("fetch/homemultidata", async () => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata");
return res.data;
});
const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: [],
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload;
},
changeRecommends(state, { payload }) {
state.recommends = payload;
},
},
//2.
extraReducers: {
[fetchHomeMultidataAction.pending](state, action) {
console.log("fetchHomeMultidataAction pending");
},
[fetchHomeMultidataAction.fulfilled](state, { payload }) {
state.banners = payload.data.banner.list;
state.recommends = payload.data.recommend.list;
},
[fetchHomeMultidataAction.rejected](state, action) {
console.log("fetchHomeMultidataAction rejected");
},
},
});
export const { changeBanners, changeRecommends } = homeSlice.actions;
export default homeSlice.reducer;
extraReducers第二种写法(函数式写法)
可以向 builder 中添加 case 来监听异步操作的结果
extraReducers: (builder) => {
builder
.addCase(fetchHomeMultidataAction.pending, (state, action) => {
console.log("fetchHomeMultidataAction-pending");
})
.addCase(fetchHomeMultidataAction.fulfilled, (state, { payload }) => {
state.banners = payload.data.banner.list;
state.recommends = payload.data.recommend.list;
});
};
函数式写法还有一个用法
安装时,我们选择 react-router-dom;
react-router 会包含一些 react-native 的内容,web 开发并不需要;
npm install react-router-dom
root.render(
<HashRouter>
<App />
</HashRouter>
);
<Routes>
<Route path="/home" element={<Home />}>
<Route path="/home/recommend" element={<HomeRecommend />} />
</Route>
</Routes>
to 属性:Link 中最重要的属性,用于设置跳转到的路径
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
需求:路径选中时,对应的 a 元素变为红色
<NavLink to="/home" style={({isActive}) => ({color: isActive ? "red": ""})}>首页</NavLink>
<NavLink to="/about" style={({isActive}) => ({color: isActive ? "red": ""})}>关于</NavLink>
默认的 activeClassName: 事实上在默认匹配成功时,NavLink 就会添加上一个动态的 active class; 所以我们也可以直接编写样式
如果你担心这个 class 在其他地方被使用了,出现样式的层叠,也可以自定义 class
<NavLink to="/home" className={({isActive}) => isActive?"link-active":""}>首页</NavLink>
<NavLink to="/about" className={({isActive}) => isActive?"link-active":""}>关于</NavLink>
Navigate
Navigate 用于路由的重定向,当这个组件出现时,就会执行跳转到对应的 to 路径中:
<Route path="/" element={<Navigate to="/home" />} />
<Route path="*" element={<NotFound />} />
<Routes>
<Route path="/home" element={<Home />}>
<Route path="/home" element={<Navigate to="/home/recommend" />} />
<Route path="/home/recommend" element={<HomeRecommend />} />
<Route path="/home/ranking" element={<HomeRanking />} />
</Route>
</Routes>
函数式使用 hooks 跳转
export function App(props) {
const navigate = useNavigate();
function navigateTo(path) {
navigate(path);
}
return (
<div className="app">
<div className="nav">
<button onClick={(e) => navigateTo("/category")}>分类</button>
<span onClick={(e) => navigateTo("/order")}>订单</span>
</div>
<div className="content">
<Routes>
<Route path="/category" element={<Category />} />
<Route path="/order" element={<Order />} />
</Routes>
</div>
</div>
);
}
类路由跳转(必须使用高阶组件封装才行,第一点实现)
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function (props) {
// 1.导航
const navigate = useNavigate();
// 2.动态路由的参数: /detail/:id
const params = useParams();
// 3.查询字符串的参数: /user?name=why&age=18
const location = useLocation();
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams);
const router = { navigate, params, location, query };
return <WrapperComponent {...props} router={router} />;
};
}
export default withRouter;
使用时候:
import { withRouter } from "../hoc";
export class Home extends PureComponent {
navigateTo(path) {
const { navigate } = this.props.router;
navigate(path);
}
render() {
return (
<div>
<button onClick={(e) => this.navigateTo("/home/songmenu")}>歌单</button>
{/* 占位的组件 */}
<Outlet />
</div>
);
}
}
export default withRouter(Home);
传递参数有二种方式:
高阶组件:
import { useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
// 高阶组件: 函数
function withRouter(WrapperComponent) {
return function (props) {
// 1.导航
const navigate = useNavigate();
// 2.动态路由的参数: /detail/:id
const params = useParams();
// 3.查询字符串的参数: /user?name=why&age=18
const location = useLocation();
const [searchParams] = useSearchParams();
const query = Object.fromEntries(searchParams);
const router = { navigate, params, location, query };
return <WrapperComponent {...props} router={router} />;
};
}
export default withRouter;
<div className="content">{useRoutes(routes)}</div>
类似 vue
路由表:
import Home from "../pages/Home";
import HomeRecommend from "../pages/HomeRecommend";
import HomeRanking from "../pages/HomeRanking";
import HomeSongMenu from "../pages/HomeSongMenu";
// import About from "../pages/About"
// import Login from "../pages/Login"
import Category from "../pages/Category";
import Order from "../pages/Order";
import NotFound from "../pages/NotFound";
import Detail from "../pages/Detail";
import User from "../pages/User";
import { Navigate } from "react-router-dom";
import React from "react";
const About = React.lazy(() => import("../pages/About"));
const Login = React.lazy(() => import("../pages/Login"));
const routes = [
{
path: "/",
element: <Navigate to="/home" />,
},
{
path: "/home",
element: <Home />,
children: [
{
path: "/home",
element: <Navigate to="/home/recommend" />,
},
{
path: "/home/recommend",
element: <HomeRecommend />,
},
{
path: "/home/ranking",
element: <HomeRanking />,
},
{
path: "/home/songmenu",
element: <HomeSongMenu />,
},
],
},
{
path: "/about",
element: <About />,
},
{
path: "/login",
element: <Login />,
},
{
path: "/category",
element: <Category />,
},
{
path: "/order",
element: <Order />,
},
{
path: "/detail/:id",
element: <Detail />,
},
{
path: "/user",
element: <User />,
},
{
path: "*",
element: <NotFound />,
},
];
export default routes;
路由懒加载:
参数:初始化值,如果不设置为 undefined;
返回值:数组,包含两个元素;
useState 的返回值是一个数组,我们可以通过数组的解构,来完成赋值会非常方便
也可以在一个组件中定义多个变量和复杂变量(数组、对象)
使用它们会有两个额外的规则:
import React, { memo, useState } from "react";
const App = memo(() => {
const [message, setMessage] = useState("zimo");
function changeMessage() {
setMessage("紫陌");
}
return (
<div>
<h2>App:{message}</h2>
<button onClick={changeMessage}>修改文本</button>
</div>
);
});
export default App;
为什么要在 Effect 中返回一个函数?
React 何时清除 effect?
import React, { memo, useEffect, useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
// 负责告知React,在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
console.log("监听redux中数据变化, 监听eventBus中的zimo事件");
// 返回值:回调函数 => 组件被重新渲染或者组件卸载的时候执行
return () => {
console.log("取消监听redux中数据变化, 取消监听eventBus中的zimo事件");
};
});
return (
<div>
<button onClick={(e) => setCount(count + 1)}>+1({count})</button>
</div>
);
});
export default App;
import React, { memo, useEffect } from "react";
import { useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
// 负责告知react, 在执行完当前组件渲染之后要执行的副作用代码
useEffect(() => {
// 1.修改document的title(1行)
console.log("修改title");
});
// 一个函数式组件中, 可以存在多个useEffect
useEffect(() => {
// 2.对redux中数据变化监听(10行)
console.log("监听redux中的数据");
return () => {
// 取消redux中数据的监听
};
});
useEffect(() => {
// 3.监听eventBus中的why事件(15行)
console.log("监听eventBus的zimo事件");
return () => {
// 取消eventBus中的zimo事件监听
};
});
return (
<div>
<button onClick={(e) => setCount(count + 1)}>+1({count})</button>
</div>
);
});
export default App;
useEffect实际上有两个参数:
如果一个函数我们不希望依赖任何的内容时,也可以传入一个空的数组 []:
import React, { memo, useEffect } from "react";
import { useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("Hello World");
useEffect(() => {
console.log("修改title:", count);
}, [count]);
useEffect(() => {
console.log("监听redux中的数据");
return () => {};
}, []);
useEffect(() => {
console.log("监听eventBus的why事件");
return () => {};
}, []);
useEffect(() => {
console.log("发送网络请求, 从服务器获取数据");
return () => {
console.log("会在组件被卸载时, 才会执行一次");
};
}, []);
return (
<div>
<button onClick={(e) => setCount(count + 1)}>+1({count})</button>
<button onClick={(e) => setMessage("你好啊")}>修改message({message})</button>
</div>
);
});
export default App;
在组件中使用共享的 Context 有两种方式
但是多个 Context 共享时的方式会存在大量的嵌套:
index.js-------------------------------------------------------------------------------
root.render(
<UserContext.Provider value={{name: "zimo", level: 99}}>
<ThemeContext.Provider value={{color:"red",size:18}}>
<App />
</ThemeContext.Provider>
</UserContext.Provider>
)
函数组件中---------------------------------------------------------------------------
import React, { memo, useContext } from 'react'
import { UserContext, ThemeContext } from "./context"
const App = memo(() => {
// 使用Context
const user = useContext(UserContext)
const theme = useContext(ThemeContext)
return (
<div>
<h2>User: {user.name}-{user.level}</h2>
<h2 style={{color: theme.color, fontSize: theme.size}}>Theme</h2>
</div>
)
})
export default App
当组件上层最近的 更新时,该 Hook 会触发重新渲染,并使用最新传递给 MyContext provider 的 context value 值。
注释是 useState 写法
import React, { memo, useReducer } from "react";
// import { useState } from 'react'
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
case "add_number":
return { ...state, counter: state.counter + action.num };
case "sub_number":
return { ...state, counter: state.counter - action.num };
default:
return state;
}
}
// useReducer+Context => redux
const App = memo(() => {
// const [count, setCount] = useState(0)
const [state, dispatch] = useReducer(reducer, { counter: 0, friends: [], user: {} });
// const [counter, setCounter] = useState()
// const [friends, setFriends] = useState()
// const [user, setUser] = useState()
return (
<div>
{/* <h2>当前计数: {count}</h2>
<button onClick={e => setCount(count+1)}>+1</button>
<button onClick={e => setCount(count-1)}>-1</button>
<button onClick={e => setCount(count+5)}>+5</button>
<button onClick={e => setCount(count-5)}>-5</button>
<button onClick={e => setCount(count+100)}>+100</button> */}
<h2>当前计数: {state.counter}</h2>
<button onClick={(e) => dispatch({ type: "increment" })}>+1</button>
<button onClick={(e) => dispatch({ type: "decrement" })}>-1</button>
<button onClick={(e) => dispatch({ type: "add_number", num: 5 })}>+5</button>
<button onClick={(e) => dispatch({ type: "sub_number", num: 5 })}>-5</button>
<button onClick={(e) => dispatch({ type: "add_number", num: 100 })}>+100</button>
</div>
);
});
export default App;
useCallback 实际的目的是为了进行性能的优化
import React, { memo, useState, useCallback, useRef } from "react";
// useCallback性能优化的点:
// 1.当需要将一个函数传递给子组件时, 最好使用useCallback进行优化, 将优化之后的函数, 传递给子组件
// props中的属性发生改变时, 组件本身就会被重新渲染
const Home = memo(function (props) {
const { increment } = props;
console.log("Home被渲染");
return (
<div>
<button onClick={increment}>increment+1</button>
{/* 100个子组件 */}
</div>
);
});
const App = memo(function () {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("hello");
// 闭包陷阱: useCallback
// const increment = useCallback(function foo() {
// console.log("increment")
// setCount(count+1)
// }, [count])
// 进一步的优化: 当count发生改变时, 也使用同一个函数(了解)
// 做法一: 将count依赖移除掉, 缺点: 闭包陷阱
// 做法二: useRef, 在组件多次渲染时, 返回的是同一个值
const countRef = useRef();
countRef.current = count;
const increment = useCallback(function foo() {
console.log("increment");
setCount(countRef.current + 1);
}, []);
// 普通的函数
// const increment = () => {
// setCount(count+1)
// }
return (
<div>
<h2>计数: {count}</h2>
<button onClick={increment}>+1</button>
<Home increment={increment} />
<h2>message:{message}</h2>
<button onClick={(e) => setMessage(Math.random())}>修改message</button>
</div>
);
});
export default App;
当需要将一个函数传递给子组件时, 最好使用 useCallback 进行优化, 将优化之后的函数, 传递给子组件!
优化体现在传递子组件时。 父组件重新渲染都会重新定义函数体现不到优化。所以体现在子组件。函数传给子组件,子组件重新渲染不会重新定义父组件的函数。
import React, { memo } from "react";
import { useMemo, useState } from "react";
const HelloWorld = memo(function (props) {
console.log("HelloWorld被渲染~");
return <h2>Hello World</h2>;
});
function calcNumTotal(num) {
// console.log("calcNumTotal的计算过程被调用~")
let total = 0;
for (let i = 1; i <= num; i++) {
total += i;
}
return total;
}
const App = memo(() => {
const [count, setCount] = useState(0);
// const result = calcNumTotal(50)
// 1.不依赖任何的值, 进行计算
const result = useMemo(() => {
return calcNumTotal(50);
}, []);
// 2.依赖count
// const result = useMemo(() => {
// return calcNumTotal(count*2)
// }, [count])
// 3.使用useMemo对子组件渲染进行优化
const info = useMemo(() => ({ name: "why", age: 18 }), []);
return (
<div>
<h2>计算结果: {result}</h2>
<h2>计数器: {count}</h2>
<button onClick={(e) => setCount(count + 1)}>+1</button>
<HelloWorld result={result} info={info} />
</div>
);
});
export default App;
useMemo 是给子组件传一个值的。传引用型才有优化 {} []。普通的值没有优化。因为对象和数组会重新声明。
import React, { memo, useRef } from "react";
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function showTitleDom() {
console.log(titleRef.current);
inputRef.current.focus();
}
return (
<div>
<h2 ref={titleRef}>Hello,zimo</h2>
<input type="text" ref={inputRef} />
<button onClick={showTitleDom}>查看title的DOM</button>
</div>
);
});
export default App;
import React, { memo, useRef, useState, useCallback } from "react";
let obj = null;
const App = memo(() => {
const [count, setCount] = useState(0);
const nameRef = useRef();
console.log(obj === nameRef); //true
obj = nameRef;
// 通过useRef解决闭包陷阱
const countRef = useRef();
countRef.current = count;
const increment = useCallback(() => {
setCount(countRef.current + 1);
}, []);
return (
<div>
<h2>Hello World: {count}</h2>
<button onClick={(e) => setCount(count + 1)}>+1</button>
<button onClick={increment}>+1</button>
</div>
);
});
export default App;
useRef 返回一个 ref 对象,返回的 ref 对象再组件的整个生命周期保持不变。
import React, { memo, useRef, forwardRef, useImperativeHandle } from "react";
const HelloWorld = memo(
forwardRef((props, ref) => {
const inputRef = useRef();
// 子组件对父组件传入的ref进行处理
useImperativeHandle(ref, () => {
return {
focus() {
console.log("focus");
inputRef.current.focus();
},
setValue(value) {
inputRef.current.value = value;
},
};
});
return <input type="text" ref={inputRef} />;
})
);
const App = memo(() => {
const titleRef = useRef();
const inputRef = useRef();
function handleDOM() {
// console.log(inputRef.current)
inputRef.current.focus();
// inputRef.current.value = ""
inputRef.current.setValue("哈哈哈");
}
return (
<div>
<h2 ref={titleRef}>哈哈哈</h2>
<HelloWorld ref={inputRef} />
<button onClick={handleDOM}>DOM操作</button>
</div>
);
});
export default App;
import React, { memo, useEffect, useLayoutEffect, useState } from "react";
const App = memo(() => {
const [count, setCount] = useState(100);
useLayoutEffect(() => {
console.log("useLayoutEffect");
if (count === 0) {
setCount(Math.random() + 99);
}
});
console.log("App render");
return (
<div>
<h2>count: {count}</h2>
<button onClick={(e) => setCount(0)}>设置为0</button>
</div>
);
});
export default App;
官方更推荐使用 useEffect 而不是 useLayoutEffect。
获取滚动位置
import { useState, useEffect } from "react";
function useScrollPosition() {
const [scrollX, setScrollX] = useState(0);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
function handleScroll() {
// console.log(window.scrollX, window.scrollY)
setScrollX(window.scrollX);
setScrollY(window.scrollY);
}
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
}, []);
return [scrollX, scrollY];
}
export default useScrollPosition;
localStorage 数据存储
import { useEffect, useState } from "react";
function useLocalStorage(key) {
// 1.从localStorage中获取数据, 并且数据数据创建组件的state
const [data, setData] = useState(() => {
const item = localStorage.getItem(key);
if (!item) return "";
return JSON.parse(item);
});
// 2.监听data改变, 一旦发生改变就存储data最新值
useEffect(() => {
localStorage.setItem(key, JSON.stringify(data));
}, [data]);
// 3.将data/setData的操作返回给组件, 让组件可以使用和修改值
return [data, setData];
}
export default useLocalStorage;
在之前的 redux 开发中,为了让组件和 redux 结合起来,我们使用了 react-redux 中的 connect:
在 Redux7.1 开始,提供了 Hook 的方式,我们再也不需要编写 connect 以及对应的映射函数了
useSelector 的作用是将 state 映射到组件中:
参数一:将 state 映射到需要的数据中;
参数二:可以进行比较来决定是否组件重新渲染;
useDispatch 非常简单,就是直接获取 dispatch 函数,之后在组件中直接使用即可;
我们还可以通过 useStore 来获取当前的 store 对象;
import React, { memo } from "react";
import { useSelector, useDispatch, shallowEqual } from "react-redux";
import { addNumberAction, changeMessageAction, subNumberAction } from "./store/modules/counter";
// memo高阶组件包裹起来的组件有对应的特点: 只有props发生改变时, 才会重新渲染
const Home = memo((props) => {
// 1.使用useSelector将redux中store的数据映射到组件内
const { message } = useSelector(
(state) => ({
message: state.counter.message,
}),
shallowEqual
);
const dispatch = useDispatch();
function changeMessageHandle() {
// 2.使用dispatch直接派发action
dispatch(changeMessageAction("你好啊, zimo!"));
}
console.log("Home render");
return (
<div>
<h2>Home: {message}</h2>
<button onClick={(e) => changeMessageHandle()}>修改message</button>
</div>
);
});
const App = memo((props) => {
// 1.使用useSelector将redux中store的数据映射到组件内
const { count } = useSelector(
(state) => ({
count: state.counter.count,
}),
shallowEqual
);
// 2.使用dispatch直接派发action
const dispatch = useDispatch();
function addNumberHandle(num, isAdd = true) {
if (isAdd) {
dispatch(addNumberAction(num));
} else {
dispatch(subNumberAction(num));
}
}
console.log("App render");
return (
<div>
<h2>当前计数: {count}</h2>
<button onClick={(e) => addNumberHandle(1)}>+1</button>
<button onClick={(e) => addNumberHandle(6)}>+6</button>
<button onClick={(e) => addNumberHandle(6, false)}>-6</button>
<Home />
</div>
);
});
export default App;