Appearance
扩展及最新特性
setState
更新状态有两种方式, 分别为对象式和函数式
1. 对象式
js
setState(stateChange, [callback]);
stateChange
为状态改变对象callback
是可选的回调函数, 在状态更新完毕, 界面也更新(render)后才被调用
jsx
state = { count: 0 };
add = () => {
this.setState({ count: this.state.count + 1 }, () => {
console.log(this.state.count); //1
});
console.log(this.state.count); //0
};
this.setState
后续更新状态是异步操作, 在回调函数中可以拿到更新完的状态值
2. 函数式
js
setState(updater, [callback]);
updater
为返回stateChange
对象的函数, 它可以接收到state
和props
callback
是可选的回调函数, 在状态更新完毕, 界面也更新(render)后才被调用
jsx
state = { count: 0 };
add = () => {
this.setState((state, props) => {
return { count: state.count + props.step };
});
};
使用规则
- 新状态不依赖原状态, 使用对象式更为简便
- 新状态依赖原状态, 使用函数式更简便
Hooks
Hook 是 React 16.8.0 增加的新特性,让我们能在函数式组件中使用 state 和其他特性
State Hook
jsx
const [xxx, setXxx] = React.useState(initValue);
initValue
是第一次初始化指定的值, 并在内部缓存- 返回值:包含 2 个元素的数组,分别为状态值和状态更新函数
jsx
const [count, setCount] = React.useState(0);
const [name, setName] = React.useState("Tom");
function add() {
setCount(count + 1);
// 函数式更新方法
// setCount((count) => count + 1);
setName("Jack");
}
有两种更新方式, 当有异步需求时使用函数式, 可以获取到之前状态值
- 更新对象数组
jsx
const [arr, setArr] = useState([]);
setArr([1, ...arr]);
const [obj, setObj] = useState({});
setObj({ a: 1, ...obj });
Effect Hook
在函数式组件中能执行副作用操作(模拟生命周期钩子)
- 语法
jsx
React.useEffect(() => {
// 执行任何副作用操作(请求数据、开启定时器...)
return () => {
// 组件卸载之前的操作(清除定时器等)
};
}, [stateValue]); //可以不写、传[]、传[xxx, xxx, ...]
第二个参数的三种状态
不写时, 监听所有状态的改变
jsxconst [count, setCount] = useState(0); const [name, setName] = useState("Jack"); React.useEffect(() => { // 初次挂载会触发 // 每次只要 count 或 name 状态改变更新, 都会触发 console.log("状态更新了"); return () => { // ... }; });
传 [] 表示不监听任何状态更新
jsxconst [count, setCount] = useState(0); const [name, setName] = useState("Jack"); React.useEffect(() => { // 只有初次挂载会触发 console.log("状态更新了"); return () => { // ... }; }, []);
传具体参数表示监听具体状态的改变
jsxconst [count, setCount] = useState(0); const [name, setName] = useState("Jack"); React.useEffect(() => { // 初次挂载会触发 // 每次只要且是 name 的状态改变更新, 才会触发 console.log("状态更新了"); return () => { // ... }; }, [name]);
Ref Hook
类似于类组件中 React.createRef()
jsx
function Demo() {
const myRef = React.useRef();
function show() {
console.log(myRef.current.value);
}
return (
<div>
<input type="text" ref={myRef} />
<button onClick={show}>展示数据</button>
</div>
);
}
Fragment
Fragment
标签本身不会被渲染成一个真实DOM
标签,类似Vue
的template
Fragment
标签可以传递key
属性,遍历时候可用
jsx
import React, { Component, Fragment } from "react";
export default class Demo extends Component {
render() {
return (
<Fragment key={1}>
<input type="text" />
</Fragment>
);
}
}
Context
一种组件间通信方式,常用于祖组件与后代组件间通信
上下文容器
js
// context.js
import React from "react";
// 创建Context容器对象
export const MyContext = React.createContext();
export const { Provider, Consumer } = MyContext;
父组件
jsx
import React, { Component } from "react";
import { Provider } from "./context.js";
export default class A extends Component {
state = { name: "tony", age: 29 };
render() {
const { name, age } = this.state;
return (
<div className="parent">
<h2>A组件</h2>
{/* 用 Provider 包裹子组件, 通过 value 向后代组件传值 */}
<Provider value={{ name, age }}>
<B />
</Provider>
</div>
);
}
}
子组件
jsx
class B extends Component {
render() {
return (
<div className="child">
<h2>B组件</h2>
<C />
</div>
);
}
}
孙组件
- 类式组件写法
jsx
import { MyContext } from "./context.js";
class C extends Component {
// 声明接收context
static contextType = MyContext;
render() {
return (
<div className="grand">
<h2>C组件</h2>
<h4>A组件中接收的姓名:{this.context.name}</h4>
</div>
);
}
}
- 函数式写法
jsx
import { Consumer } from "./context.js";
function C() {
return (
<div className="grand">
<h2>C组件</h2>
<h4>
A组件中接收的姓名:
<Consumer>{(value) => `${value.name};年龄是${value.age}`}</Consumer>
</h4>
</div>
);
}
组件优化
两个问题导致组件更新效率低
- 只要调用
setState()
,即使没有修改状态,组件也会重新render()
- 只要父组件重新渲染,即使子组件没有使用父组件的状态,也会重新
render()
原因: shouldComponentUpdate()
钩子默认总是返回 true
期望效果: 只有组件的 state
或 props
的数据发生改变时才重新渲染
解决
使用 PureComponent
,它重写了 shouldComponentUpdate()
, 只有 state
或 props
数据有变化才返回 true
jsx
import React, { PureComponent } from 'react'
class Demo extends PureComponent {
...
addStu = () => {
// 不会渲染
const { stus } = this.state
stus.unshift('小刘')
this.setState({ stus })
// 重新渲染
const { stus } = this.state
this.setState({ stus: ['小刘', ...stus] })
}
...
}
TIP
- 它只是进行 state 和 props 数据的浅比较, 如果只是数据对象内部数据变了, 返回 false。即对于引用数据类型,比较的是地址引用
- 不要直接修改 state 数据, 而是要产生新数据
render props
将子组件通过 render()
回调传给父组件, 父组件内部定义占位形成嵌套,类似于 vue 中的插槽
jsx
import React, { Component } from "react";
export default class Parent extends Component {
render() {
return (
<div>
<h3>父组件</h3>
{/* 通过 render 回调将 B 组件传给 A组件 */}
<A render={(name) => <B name={name} />} />
</div>
);
}
}
// A组件
class A extends Component {
state = { name: "tom" };
render() {
const { name } = this.state;
return (
<div>
<h3>A组件</h3>
{/* 占位, 调用 render() 得到 B组件,render内定义要传的参数 */}
{this.props.render(name)}
</div>
);
}
}
// B组件
class B extends Component {
render() {
return (
<div>
<h3>B组件,{this.props.name}</h3>
</div>
);
}
}
错误边界
- 只能捕获后代组件生命周期产生的错误,不捕获自己组件和其他组件在合成事件、定时器中产生的错误
- 只在生产环境(项目上线)起效
子组件(抛出错误)
jsx
export default class Child extends Component {
state = {
// 故意传递一个字符串,引起错误
users: "abc",
};
render() {
return (
<div>
{this.state.users.map((item) => {
return <p key={item.id}>{item.name}</p>;
})}
</div>
);
}
}
父组件(捕获异常)
jsx
import React, { Component } from "react";
import Child from "./Child";
export default class Parent extends Component {
state = {
hasError: "", //用于标识子组件是否产生错误
};
// 当子组件出现错误,会触发调用,并携带错误信息
static getDerivedStateFromError(error) {
// render 之前触发; 返回新的 state
return { hasError: error };
}
// 子组件产生错误时调用该钩子
componentDidCatch(error, info) {
console.log(error, info);
console.log("此处统计错误,反馈给服务器");
}
render() {
return (
<div>
<h2>Parent组件</h2>
{this.state.hasError ? <h2>网络不稳定,稍后再试</h2> : <Child />}
</div>
);
}
}