函数组件
函数组件和 class 组件的区别
函数组件:
- 纯函数,输入
props
,输出JSX
- 没有实例,没有生命周期,没有
state
- 不能扩展其他方法
// class 组件
Class List extends React.Component {
constructor(props) {
super(props);
}
render() {
const { list } = this.props;
return <ul>
{
list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>;
})
}
</ul>;
}
}
// 函数组件
function List(props) {
const { list } = this.props;
return <ul>
{
list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>;
})
}
</ul>;
}
受控和非受控组件
受控组件
- 表单的值受
state
控制。 value
指向state
,onChange
事件监听,使用setState
修改值。
非受控组件
ref
defaultValue
、defaultChecked
- 手动操作
dom
元素
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'zhangsan',
flag: true,
};
this.nameInputRef = React.createRef(); // 创建 ref
this.fileInputRef = React.createRef();
}
render() {
// // input defaultValue
// return <div>
// {/* 使用 defaultValue 而不是 value ,使用 ref */}
// <input defaultValue={this.state.name} ref={this.nameInputRef}/>
// {/* state 并不会随着改变 */}
// <span>state.name: {this.state.name}</span>
// <br/>
// <button onClick={this.alertName}>alert name</button>
// </div>;
// // checkbox defaultChecked
// return <div>
// <input
// type="checkbox"
// defaultChecked={this.state.flag}
// />
// </div>;
// file
return <div>
<input type="file" ref={this.fileInputRef}/>
<button onClick={this.alertFile}>alert file</button>
</div>;
}
alertName = () => {
const elem = this.nameInputRef.current; // 通过 ref 获取 DOM 节点
alert(elem.value); // 不是 this.state.name
}
alertFile = () => {
const elem = this.fileInputRef.current; // 通过 ref 获取 DOM 节点
alert(elem.files[0].name);
}
}
export default App;
非受控组件使用场景
必须手动操作 DOM
元素,setState
实现不了
- 比如:文件上传
<input type="file" />
- 某些富文本编辑器,需要传入
DOM
元素
Portals
让组件渲染到父组件之外。
背景
组件默认会按既定层次嵌套渲染,如何让组件渲染到父组件之外?
基本使用
import React from 'react';
import ReactDOM from 'react-dom';
import './style.css';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
};
}
render() {
// // 正常渲染
// return <div className="modal">
// {this.props.children} {/* vue slot */}
// </div>;
// 使用 Portals 渲染到 body 上。
// fixed 元素要放在 body 上,有更好的浏览器兼容性。
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
);
}
}
export default App;
使用场景
overflow: hidden
- 父组件
z-index
值太小 fixed
需要放在body
第一层
context
背景
- 公共信息(语言、主题)如何传递给每个组件?
- 用
props
太繁琐 - 用
redux
小题大做
和 vue
的 provide / inject
功能类似。
基本使用
import React from 'react';
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light');
// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context; // 会报错。函数式组件没有实例,即没有 this
// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
{ value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>;
}
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext; // 也可以用 ThemedButton.contextType = ThemeContext;
render() {
// React 会往上找到最近的 theme Provider,然后使用它的值。
const theme = this.context;
return <div>
<p>button's theme is {theme}</p>
</div>;
}
}
// 指定 contextType 读取当前的 theme context。
ThemedButton.contextType = ThemeContext;
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: 'light'
};
}
render() {
return <ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr/>
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>;
}
changeTheme = () => {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
});
}
}
export default App;
异步组件(懒加载)
import()
React.lazy
React.Suspense
通过 React.lazy
引入异步组件,由于异步加载的时候可能会出现等待,所以可以使用 React.Suspense
来定义一个 loading
。
import React from 'react';
const ContextDemo = React.lazy(() => import('./ContextDemo'));
class App extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo/>
</React.Suspense>
</div>;
// 1. 强制刷新,可看到 loading
// 2. 看 network 的 js 加载
}
}
export default App;
性能优化
shouldComponentUpdate(简称 SCU)
在 React
中父组件更新,子组件无条件更新。
基本用法
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count) {
return true; // 可以渲染
}
return false; // 不重复渲染
}
默认返回 true
- 在
React
中父组件更新,则子组件无条件更新。
import React from 'react';
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
title: ''
};
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>;
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
});
}
onSubmit = () => {
const { submitTitle } = this.props;
submitTitle(this.state.title); // 'abc'
this.setState({
title: ''
});
}
}
class List extends React.Component {
constructor(props) {
super(props);
}
render() {
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>;
})}</ul>;
}
}
class Footer extends React.Component {
constructor(props) {
super(props);
}
render() {
return <p>
{this.props.text}
{this.props.length}
</p>;
}
componentDidUpdate() {
console.log('footer did update');
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.text !== this.props.text
|| nextProps.length !== this.props.length) {
return true; // 可以渲染
}
return false; // 不重复渲染
}
// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props);
// 状态(数据)提升
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
],
footerInfo: '底部文字'
};
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
<Footer text={this.state.footerInfo} length={this.state.list.length}/>
</div>;
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
});
}
}
export default TodoListDemo;
SCU 一定要配合不可变值
import React from 'react';
import _ from 'lodash';
class Input extends React.Component {
constructor(props) {
super(props);
this.state = {
title: ''
};
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>;
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
});
}
onSubmit = () => {
const { submitTitle } = this.props;
submitTitle(this.state.title);
this.setState({
title: ''
});
}
}
class List extends React.Component {
constructor(props) {
super(props);
}
render() {
const { list } = this.props;
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>;
}
// 增加 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// _.isEqual 做对象或者数组的深度比较(一次性递归到底)
if (_.isEqual(nextProps.list, this.props.list)) {
// 相等,则不重复渲染
return false;
}
return true; // 不相等,则渲染
}
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
};
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
</div>;
}
onSubmitTitle = (title) => {
// 正确的用法
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
});
// // 为了演示 SCU ,故意写的错误用法
// this.state.list.push({
// id: `id-${Date.now()}`,
// title
// });
// this.setState({
// list: this.state.list
// });
}
}
export default TodoListDemo;
纯组件(PureComponent 和 React.memo)
PureComponent
:SCU
实现了浅比较;用于class
组件React.memo
:函数组件中的PureComponent
- 浅比较已适用大部分情况,尽量不要做深度比较(比较耗费性能)
function MyComponent(props) {
// 使用 props 渲染
}
function areEqual(prevProps, nextProps) {
// 如果把 nextProps 传入 render 方法的返回结果与 将 prevProps 传入 render 方法的返回结果 一致则返回 true,否则返回 false
}
export default React.memo(MyComponent, areEqual);
不可变值 immutable.js
- 彻底拥抱“不可变值”
- 基于共享数据(不是深拷贝),速度好
缺点:
- 有一定的迁移和学习成本。按需使用
const map1 = Immutable.Map({ a: 1, b: 2, c: 3 });
const map2 = map1.set('b', 50);
map1.get('b'); // 2
map2.get('b'); // 50
关于组件公共逻辑的抽离
- 高阶组件
HOC
Render Props
高阶组件 HOC
高阶组件基本用法
- 高阶组件的本质是一个工厂函数(相当于是给组件包裹了一层父组件,然后将属性通过
props
透传给子组件) - 主要解决业务逻辑复用的问题
// 高阶组件不是一种功能,而是一种模式
const HOCFactory = Component => {
class HOC extends Rect.component {
// 在此定义多个组件的公共逻辑
render() {
// 返回拼装的结果
return <Component {...this.props} />;
}
}
return HOC;
}
const EnhancedComponent1 = HOCFactory(WrappedComponent1);
const WnhancedComponent2 = HOCFactory(WrappedComponent2);
举例:鼠标滑过获取位置抽离为高阶组件
import React from 'react';
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
);
}
}
return withMouseComponent;
}
const App = (props) => {
const a = props.a;
const { x, y } = props.mouse; // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App); // 返回高阶函数
redux 的 connect 是高阶组件
import { connenct } from 'react-redux';
// connect 是高阶组件
const VisibleTodoList = connect(mapStateToProps, mapDispatchToProps)(TodoList);
export default VisibleTodoList;
export const connect = (mapStateToProps, mapDispatchToProps) => {
return WarappedComponent => {
class Connect extends Component {
constructor() {
super();
this.state = {
allprops: {}
};
}
// 省略...
render() {
return <WrappedComponent {...this.state.allProps} />
}
}
return Connect;
};
}
Vue 如何实现高阶组件
在工厂函数中,实例化一个 vue
组件,在这个新实例里面实现公用逻辑,然后在 render
函数中传入 Component
,并且带上 props
值。
const HOCFactory = Component => {
const instance = new Vue({
// 实现公共组件逻辑...
created () {},
mounted () {},
data () {},
// 实现公共组件逻辑...
render (h) => h(Component, {
props: {
// 传入props
}
})
});
return instance;
}
const EnhancedComponent1 = HOCFactory(Component1);
const EnhancedComponent2 = HOCFactory(Component2);
Render Props
通过一个函数将 class
组件的 state
作为 props
传递给纯函数组件。
基本使用
// Render Props 的核心思想
// 通过一个函数将 class 组件的 state 作为 props 传递给纯函数组件
class Factory extends React.component {
constructor() {
this.state = {
// 多个组件的公共逻辑的数据
};
}
// 修改 state
render() {
// 通过 render 函数将 state 传递给纯函数组件
return <div>{this.props.render(this.state)}</div>;
}
}
const App = () => {
return <Factory render={
// render 是一个函数组件
(props) => <p>{props.a} {props.b} ...</p/>
} />;
};
举例:用 Render Props 实现鼠标滑过获取位置的逻辑复用
import React from 'react';
import PropTypes from 'prop-types';
class Mouse extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
};
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
);
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
};
const App = (props) => {
return (<div style={{ height: '500px' }}>
<p>{props.a}</p>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
}/>
</div>);
};
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App;
高阶组件 和 Render Props 的区别
HOC
:模式简单,但会增加组件层级Render Props
:代码简洁,学习成本较高
FQA
- 函数组件和
class
组件的区别。
函数组件:
- 纯函数,输入props
,输出JSX
- 没有实例,没有生命周期,没有state
- 不能扩展其他方法 - 受控组件和非受控组件的区别。
受控组件:
-value
受state
控制,通过onChnage
监听事件,setState
修改值
非受控组件:
-value
不受state
控制,通过defaultValue
指定默认值,通过ref
获取dom
节点 Portals
是什么?- 将组件渲染到父组件之外
React
为什么要加shouldComponentUpdate
这个可选的生命周期,为什么不直接在React
中作对比?- 因为大部分情况下,没这个必要。如果项目不是太复杂,用不用
SCU
效果都一样,那就别用。所以给开发者一个可定制的权利,如果项目很复杂,可以选择使用。
- 因为大部分情况下,没这个必要。如果项目不是太复杂,用不用
react
如何实现组件公共逻辑的抽离?- 高阶组件
HOC
就是一个函数中定义 一个组件, 组件做了公用的逻辑, 并且接受一个组件作为参数,返回这个参数组件,并且把公用组件的state
传给参数组件
Render Props
render props
就是给公用组件添加一个属性, 属性值是个函数,函数在公用组件中被调用,且传入了需要的参数
- 高阶组件