函数组件:
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
元素让组件渲染到父组件之外。
组件默认会按既定层次嵌套渲染,如何让组件渲染到父组件之外?
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
第一层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;
在 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;
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
: 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);
缺点:
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
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); // 返回高阶函数
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
组件,在这个新实例里面实现公用逻辑,然后在 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);
通过一个函数将 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/>
} />;
};
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;
HOC
:模式简单,但会增加组件层级Render Props
:代码简洁,学习成本较高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
就是给公用组件添加一个属性, 属性值是个函数,函数在公用组件中被调用,且传入了需要的参数