react - (1)基础概念

复习一下react基础只是,写vue写多了,基本的语法都忘记了,现在react 16之后有一些变更,例如很多周期函数后面就不用了,也引入react hook使得函数组件可以使用state和一些类似组件的特性,所以还是更新一下,方便后续技术栈切换有细节的遗漏…

react基础

1) ReactDOM.render

react.js 是 React 的核心库

react-dom.js 是提供与 DOM 相关的功能,用于将模板转为 HTML 语言,并插入指定的 DOM 节点

将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()

1
2
3
4
ReactDOM.render(
<h1>Hello, world!</h1>, // react元素
document.getElementById('example') // dom节点
);

2) jsx

它被称为 JSX,是一个 JavaScript 的语法扩展

遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析

给JSX元素加class要用className代替

1
ReactDOM.render(<h1 className="bg">hello world</h1>, document.getElementById('root'))

标签的for属性要使用htmlFor代替

1
2
3
4
5
6
7
8
9
let ele = (
<div>
<label htmlFor="username">
用户名:
</label>
<input type="text" id="username"/>
</div>
)
ReactDOM.render(ele, document.getElementById('root'))

style必须是一个对象的形式

1
ReactDOM.render(<h1 style={{background: 'red'}}>hello world</h1>, document.getElementById('root'))

JSX 标签的第一部分指定了 React 元素的类型。大写字母开头的 JSX 标签意味着它们是 React 组件。这些标签会被编译为对命名变量的直接引用

1
2
3
4
5
6
7
import React from 'react'; // 必须引入react
import CustomButton from './CustomButton';

function WarningButton() {
// return React.createElement(CustomButton, {color: 'red'}, null);
return <CustomButton color="red" />;
}

使用引号,来将属性值指定为字符串字面量,使用大括号,来在属性值中插入一个 JavaScript 表达式,在属性中嵌入 JavaScript 表达式时,不要在大括号外面加上引号

1
<div age={age} gender="man">{username}</div>

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    const element = (
<h1 className="greeting">
Hello, world!
</h1>
);

// 等价于

const element = const element = React.createElement(
'h1', // element
{
className: 'greeting'
},// config
'Hello, world!' // text
);
);

一个标签里面没有内容,你可以使用 /> 来闭合标签

1
2
3
4
5
6
7
8
const element = <img src={user.avatarUrl} />;

// 等价于
React.createElement(
'img',
{src: 'xxxx'},
null
)

在 JSX 类型中使用点语法

1
2
3
4
5
6
7
8
9
10
11
import React from 'react';

const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}

function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />; // 使用点语法调用react组件
}

动态组件

vue中通过components的is属性动态加载组件,react中要定义一个大写字母开头的变量,在jsx会解析成react元素

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';
import { PhotoStory, VideoStory } from './stories';

const components = {
photo: PhotoStory,
video: VideoStory
};

function Story(props) {
// 正确!JSX 类型可以是大写字母开头的变量。
const SpecificStory = components[props.storyType];
return <SpecificStory story={props.story} />;
}

JSX 防止注入攻击

1
2
3
const title = response.potentiallyMaliciousInput; // 用户的潜在危险输入
// 直接使用是安全的:
const element = <h1>{title}</h1>;
  • React DOM 在渲染所有输入内容之前,默认会进行转义。它可以确保在你的应用中,永远不会注入那些并非自己明确编写的内容。所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(cross-site-scripting, 跨站脚本)攻击。

当props插入HTML需要dangerouslySetInnerHTML使用转译

1
2
let str = '<div>如果我是可能带有xss攻击的代码</div>'
React.render(<h1 dangerouslySetInnerHTML={{__html: str}}></h1>, document.getElementById('root'))

多行的时候建议将内容包裹在括号中,虽然这样做不是强制要求的,但是这可以避免遇到自动插入分号陷阱

1
2
3
4
5
6
7
let userInfo = (
<ul>
<li>{username}</li>
<li>{gender}</li>
<li>{age}</li>
</ul>
);

HTML 语言直接写在 JavaScript 语言之中,不加任何引号,它允许 HTML 与 JavaScript 的混写

1
2
3
4
5
6
7
8
9
let names = ['lucy','lily']
ReactDOM.render(
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
},
document.getElementById('example') // dom节点
);

JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员

1
2
3
4
5
6
7
8
var arr = [ // 将react元素存入变量
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);

React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是在script中单独使用 JSX 的地方,都要加上 type=”text/babel”

3) 组件 - 函数组件和class组件

  • 组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思

  • 组件的返回值只能有一个根元素,一般可以是用React.fragment进行包裹,有点类似vue中template

  • 将函数组件转换成 class 组件

1
2
3
4
5
6
7
class Comp extends React.Component {  // 1.创建一个同名的 ES6 class,并且继承于 React.Component
render(){ // 2.添加一个空的 render() 方法。
// 3. 将函数体移动到 render() 方法之中。
// 4. 在 render() 方法中使用 this.props 替换 props。
}
}
// 5.删除剩余的空函数声明。
  • 组件名称必须以大写字母开头

会将以小写字母开头的组件视为原生 DOM 标签

组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素

  • 函数组件与 class 组件
函数组件

函数组件接收一个单一的 props 对象并返回了一个React元素

定义组件最简单的方式就是编写 JavaScript 函数,函数组件本质上就是 JavaScript 函数。

1
2
3
4
5
// 函数里面是没有this
// 函数组件没有生命周期,状态等,react16.8后需要引入hook才可以有state和作用能力
function Welcome(props) { // 接受的参数就是props
return <h1>Hello, {props.name}</h1>; // 返回一个react元素
}
类组件

你同时还可以使用 ES6 的 class 来定义组件:

1
2
3
4
5
6
7
8
9
10
11
12
export default class Welcome extends React.Component { // 定义 class 组件,需要继承 React.Component
constructor(props) {
super(props); // 继承基类的属性props
this.state = {
date: new Date()
}; // 初始化state维护自己组件内部的状态
}

render() { // 在 React.Component 的子类中有个必须定义的 render() 函数
return <h1>Hello, {this.props.name}</h1>;
}
}
组件总结
1
2
3
4
5
6
- 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
- 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
- React是单项数据流,父组件改变了属性,那么子组件视图会更新。
- 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改
- 组件的属性和状态改变都会更新视图。
- 函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。为了提高性能,尽量使用函数组件。

4) 组件的生命周期

  • 挂载

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

1
2
3
4
constructor()
static getDerivedStateFromProps()
render()
componentDidMount()
  • 更新

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

1
2
3
4
5
static getDerivedStateFromProps()
shouldComponentUpdate()
render()
getSnapshotBeforeUpdate()
componentDidUpdate()
  • 卸载

当组件从 DOM 中移除时会调用如下方法:

1
componentWillUnmount()
  • 错误处理

当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

1
2
static getDerivedStateFromError()
componentDidCatch()

5) Props

  • 当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)转换为单个对象传递给组件,这个对象被称之为 “props”
1
2
3
4
5
6
7
8
9
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />; // 将 {name: 'Sara'} 作为 props 传递给Welcome组件
ReactDOM.render(
element,
document.getElementById('root')
);
  • Props 默认值为 “True

如果你没给 prop 赋值,它的默认值是 true

1
2
3
<MyTextBox autocomplete /> // 等价于

<MyTextBox autocomplete={true} />
  • 属性展开

如果你已经有了一个 props 对象,你可以使用展开运算符 … 来在 JSX 中传递整个 props 对象

1
2
3
4
<Greeting firstName="Ben" lastName="Hector" />;

const props = {firstName: 'Ben', lastName: 'Hector'};
return <Greeting {...props} />;

你还可以选择只保留当前组件需要接收的 props,并使用展开运算符将其他 props 传递下去。

1
2
3
4
5
    const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};
  • Props 的只读性

组件无论是使用函数声明还是通过 class 声明,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

  • this.props.children

包含在开始和结束标签之间的 JSX 表达式内容将作为特定属性 props.children 传递给外层组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class RootContent extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<div className='divider'>
{this.props.children} // 将RootChild中RootContent中的react元素全部放到这里
</div>
);
}
}
class RootChild extends React.Component{
constructor(props){
super(props);
}
render(){
return (
<RootContent>
<p>Hello, React</p>
<p>Hello, Redux</p>
<p>Hello, Facebook</p>
<p>Hello, Google</p>
</RootContent>
);
}
}
ReactDOM.render(
<RootChild />,
document.querySelector('#root')
);
  • props类型校验
1
2
3
4
5
6
7
8
9
10
11
12
13
import PropTypes from 'prop-types';

class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}

Greeting.propTypes = {
name: PropTypes.string
};
  • 限制单个元素

PropTypes.element 来确保传递给组件的 children 中只包含一个元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mport PropTypes from 'prop-types';

class MyComponent extends React.Component {
render() {
// 这必须只有一个元素,否则控制台会打印警告。
const children = this.props.children;
return (
<div>
{children}
</div>
);
}
}

MyComponent.propTypes = {
children: PropTypes.element.isRequired
};
  • 默认prop值

可以通过配置特定的 defaultProps 属性来定义 props 的默认值:

1
2
3
4
5
6
7
8
9
10
11
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};

5) state

  • state 包含了随时可能发生变化的数据。state 由用户自定义,它是一个普通 JavaScript 对象,并且完全受控于当前组件

class 构造函数,然后在该函数中为 this.state 赋初值

1
2
3
4
constructor(props) {
super(props);
this.state = {date: new Date()}; // 赋初值
}
  • 请把 this.state 看作是不可变的,永远不要直接改变 this.state,因为后续调用的 setState() 可能会替换掉你的改变

不要直接修改 State,构造函数是唯一可以给 this.state 赋值的地方:

1
2
// Wrong
this.state.username = 'pis';

使用setState 修改 State。setState() 的第一个参数除了接受函数外,还可以接受对象类型

1
2
3
4
5
6
7
8
this.setState({ //异步更新,并且会合并多次状态变化一次更新
username:'pis'
})

//如果后续状态取决于当前状态,我们建议使用 updater 函数的形式代替
this.setState((state, props) => { // 传入一个更新函数,就可以访问当前状态值。
return {username: state.username + props.step};
});

同一周期内会对多个 setState 进行批处理,会将传入的对象浅层合并到新的 state 中

1
2
3
4
5
6
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1}, // 后调用的 setState() 将覆盖同一周期内先调用 setState 的值,因此商品数仅增加一次
...
)

setState() 将对组件 state 的更改排入队列,并通知 React 需要使用更新后的 state 重新渲染此组件及其子组件。这是用于更新用户界面以响应事件处理器和处理服务器数据的主要方式

除非shouldComponentUpdate() 返回 false,否则 setState() 将始终执行重新渲染操作

将 setState() 不是立即更新组件,它会批量推迟更新componentDidUpdate 或者 setState 的回调函数(setState(updater, callback))可以保证在应用更新后触发.

setState 通过触发一次组件的更新来引发重绘

1
2
3
4
5
6
重绘指的就是引起 React 的更新生命周期函数4个函数:

1.shouldComponentUpdate(被调用时this.state没有更新;如果返回了false,生命周期被中断,虽然不调用之后的函数了,但是state仍然会被更新)
2.componentWillUpdate(被调用时this.state没有更新)
3.render(被调用时this.state得到更新)
4.componentDidUpdate

在React中,如果是由React引发的事件处理(比如通过onClick引发的事件处理),调用 setState 不会同步更新 this.state,除此之外的setState调用会同步执行this.state。

所谓“除此之外”,指的是绕过React通过 addEventListener 直接添加的事件处理函数,还有通过setTimeout || setInterval 产生的异步调用

简单一点说, 就是经过React 处理的事件是不会同步更新 this.state的. 通过 addEventListener || setTimeout/setInterval 的方式处理的则会同步更新。

1
2
3
4
5
setTimeout(function(){
this.setState({ //异步更新,并且会合并多次状态变化
username:'pis'
})
})

6) 事件处理

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

1
2
3
4
5
// 1. React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
// 2. 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串
<button onClick={activateLasers}>
Activate Lasers
</button>

对事件this的绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 1.在构造函数中绑定this(推荐这样写)
constructor(props) {
super(props);
this.state = {isToggleOn: true};

// 为了在回调中使用 `this`,这个绑定是必不可少的
this.handleClick = this.handleClick.bind(this);
}

// 2.回调中使用箭头函数:有性能影响并且如果回调函数作为属性传给子组件的时候会导致重新渲染的问题
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>

// 3.在调用的时候使用bind绑定this,有性能影响并且如果回调函数作为属性传给子组件的时候会导致重新渲染的问题
<button onClick={this.handleClick.bind(this)}>
Click me
</button>

向事件处理程序传递参数

1
2
3
4
5
6
7
8
// 1.箭头函数,需要显示的传递e事件对象
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
// 2.Function.prototype.bind,默认形参后面会传递e
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

deleteRow(id,e){
console.log(id,e)
}

6) 条件渲染

  • 你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。

  • React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。

  • 元素变量 - 你可以使用变量来储存元素。 它可以帮助你有条件地渲染组件的一部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
render() {
const isLoggedIn = this.state.isLoggedIn;
let button;

if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />; // 使用变量来储存元素
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}

return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
  • 与运算符 &&

在 JavaScript 中,true && expression 总是会返回 expression, 而 false && expression 总是会返回 false。因此,如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。

1
2
3
4
5
6
7
8
9
10
  return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
  • 或运算符 ||
  • 三目运算符 condition ? true : false
1
2
3
4
5
6
7
8
9
10
11
12
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
  • 阻止组件渲染

极少数情况下,你可能希望能隐藏组件,即使它已经被其他组件渲染。若要完成此操作,你可以让 render 方法直接返回 null,而不进行任何渲染

1
2
3
4
5
6
7
8
9
10
11
function WarningBanner(props) {
if (!props.warn) {
return null;
}

return (
<div className="warning">
Warning!
</div>
);
}

在组件的 render 方法中返回 null 并不会影响组件的生命周期

  • 使用 Javascript 中的 map() 方法渲染多个组件
1
2
3
4
5
6
7
8
9
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.id}>{number}</li> // 是当你创建一个元素时,必须包括一个特殊的 key 属性
);

ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
  • key

key 帮助 React 识别哪些元素改变了,比如被添加或删除。因此你应当给数组中的每一个元素赋予一个确定的标识。

一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串

元素的 key 只有放在就近的数组上下文中才有意义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    function ListItem(props) {
const value = props.value;
return (
// 错误!你不需要在这里指定 key:
<li key={value.toString()}>
{value}
</li>
);
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 错误!元素的 key 应该在这里指定:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}

key 只是在兄弟节点之间必须唯一

  • 数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值

  • key 会传递信息给 React ,但不会传递给你的组件。如果你的组件中需要使用 key 属性的值,请用其他属性名显式传递这个值

1
2
3
4
5
6
const content = posts.map((post) =>
<Post
key={post.id} // Post 组件可以读出 props.id,但是不能读出 props.key。
id={post.id}
title={post.title} />
);

7) 表单

受控组件
  • 使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”

  • input/textarea/select等都是受控组件

  • input file 因为它的 value 只读,所以它是 React 中的一个非受控组件。<input type="file" />

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
constructor(props) {
super(props);
this.state = {value: ''}; // state控制数据源

this.handleChange = this.handleChange.bind(this); // 表单输入发生变化手动触发setState更新状态
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
</form>
);
}
  • 处理多个输入

当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作

1
2
3
4
5
6
7
8
9
handleInputChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;

this.setState({
[name]: value
});
}
非受控组件
  • 要编写一个非受控组件,而不是为每个状态更新都编写数据处理函数,你可以 使用 ref 来从 DOM 节点中获取表单数据。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}

handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value); // ref获取value
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
  • 默认值

受控组件中使用value给组件加上默认值,如果在非受控组件中使用defaultValuedefaultChecked来设置默认值

1
2
<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked,
<input type="text"> ,<select> 和 <textarea> 支持 defaultValue。

文件输入本身就是一个非受控组件,因为它的值只能由用户设置,而不能通过代码控制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${
this.fileInput.current.files[0].name
}`
);
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}

ReactDOM.render(
<FileInput />,
document.getElementById('root')
);

8) 状态提升

  • 通常,多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去
1
2
3
4
<TemperatureInput
scale="c"
temperature={celsius} // 通过props分发状态
onTemperatureChange={this.handleCelsiusChange} /> // 通过事件更新父组件中的state

9) 组合 vs 继承

  • vue中通过slot的方式分发匿名和具名slot,react中通过组件可以接受任意 props,包括基本数据类型,React 元素以及函数。

  • 包含关系

有点类似vue中的匿名slot插槽,有些组件无法提前知晓它们子组件的具体内容,组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}

// 调用中
<FancyBorder>
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
<FancyBorder/>

还有具名插槽的类似使用,将一个react元素传递给props属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}

function App() {
return (
<SplitPane
left={<Contacts />} // props.left分发<Contacts />到指定的插槽
right={<Chat />} // props.right分发<Chat />到指定的插槽
/>
);
}

10) Render Props

可以看到上面这种props直接传递一个react元素的方式,可以共享代码,术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术

渲染属性指的是使用一个值为函数的prop来传递需要动态渲染的nodes或组件。如下面的代码可以看到我们的 DataProvider组件包含了所有跟状态相关的代码,而 Cat组件则可以是一个单纯的展示型组件,这样一来 DataProvider就可以单独复用了

使用函数的好处就是可以传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class DataProvider extends React.Component {
constructor(props) {
super(props);
this.state = { target:'some mouse'};
}
render() {
return (
<div>
{this.props.render(this.state)} // 将state当作参数传给render的函数
</div>
);
}
}

// 使用的地方就是 组件内部就会把Mouse组件的状态给Cat组件
<DataProvider render={data=>{
<Cat target={data.target}/>
}} />

11) Refs

  • ref字符串的方式已经移除了,之前是this.refs[‘refName’] 获取绑定在组件上的ref属性

回调refs

  • 你会传递一个函数。这个函数中接受 React 组件实例或 HTML DOM 元素作为参数,以使它们能在其他地方被存储和访问
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class CustomTextInput extends React.Component {
constructor(props) {
super(props);

this.textInput = null;

this.setTextInputRef = element => {
this.textInput = element; // 将dom对象或者react实例赋给变量textInput
};

this.focusTextInput = () => {
// 使用原生 DOM API 使 text 输入框获得焦点
if (this.textInput) this.textInput.focus(); // 调用ref变量处理ref的dom对象的focus
};
}

componentDidMount() {
// 组件挂载后,让文本框自动获得焦点
this.focusTextInput();
}

render() {
// 使用 `ref` 的回调函数将 text 输入框 DOM 节点的引用存储到 React
// 实例上(比如 this.textInput)
return (
<div>
<input
type="text"
ref={this.setTextInputRef} // ref为一个回调函数
/>
<input
type="button"
value="Focus the text input"
onClick={this.focusTextInput}
/>
</div>
);
}
}
  • ref回调传递
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function CustomTextInput(props) {
return (
<div>
<input ref={props.inputRef} />
</div>
);
}

class Parent extends React.Component {
render() {
return (
<CustomTextInput
inputRef={el => this.inputElement = el} // 通过inputRef的props将回调传给CustomTextInput组件
/>
);
}
}
// this.inputElement就可以获取到CustomTextInput中的input的dom

react 16之后的获取ref的方法

  • ref在函数式组件上不可使用,函数式组件无实例,但是其内部的dom节点和类组件可以使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function CustomTextInput(props) {
// 这里必须声明 textInput,这样 ref 才可以引用它
let textInput = React.createRef();

function handleClick() {
textInput.current.focus(); // 在函数组件内部使用ref触发原生dom的focus
}

return (
<div>
<input
type="text"
ref={textInput} />

<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
  • Ref 转发是一项将 ref 自动地通过组件传递到其一子组件的技巧

  • 在React 16.3版本后,使用React.createRef方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上,该ref的current属性将能拿到dom节点或组件的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AutoFocusTextInput extends React.Component {
constructor(props) {
super(props);
this.textInput = React.createRef();
}

componentDidMount() {
this.textInput.current.focusTextInput(); // 获取到实例CustomTextInput中的focusTextInput方法
}

render() {
return (
<CustomTextInput ref={this.textInput} />
);
}
}
  • React 16.3版本后提供的React.forwardRef,可以用来创建子组件,以传递ref

通过这样的方式就可以通过ref传递多个组件,获取组件内部的dom结构或者实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//子组件(通过forwardRef方法创建)
const Child=React.forwardRef((props,ref)=>(
<input ref={ref} />
));

//父组件
class Father extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
componentDidMount(){
console.log(this.myRef.current);
}
render(){
return <Child ref={this.myRef}/>
}
}
  • 高阶组件中ref的透传
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function logProps(Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}

render() {
const {forwardedRef, ...rest} = this.props;

// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}

// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});

// React.forwardRef 接受一个渲染函数
return React.forwardRef(
function myFunction(props, ref) {
return <LogProps {...props} forwardedRef={ref} />;
}
);
}

13) 高阶组件

高阶组件是参数为组件,返回值为新组件的函数const EnhancedComponent = higherOrderComponent(WrappedComponent);

高阶组件这个概念就更好理解了,说白了就是一个函数接受一个组件作为参数,经过一系列加工后,最后返回一个新的组件

1
2
3
4
5
6
7
8
9
10
const EnhancedComponent =  function higherOrderComponent(WrappedComponent){
class Com extends react.components{
render(){
// 对组件进行加工处理,包装一下Com再返回
}
}
return Com
}

// EnhancedComponent现在就是基于Com改造后的高阶组件

14) context上下文

  • Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
  • 这个方法用来创建context对象,并包含Provider、Consumer两个组件

数据的生产者,通过value属性接收存储的公共状态,来传递给子组件或后代组件

1
<Provider value={/* some value */}>

数据的消费者,通过订阅Provider传入的context的值,来实时更新当前组件的状态

1
2
3
<Consumer>
{value => /* render something based on the context value */}
</Consumer>

const {Provider, Consumer} = React.createContext(defaultValue);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
export const {Provider, Consumer} = React.createContext('light');

export default class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<Provider value="dark">
<Toolbar />
</Provider>
);
}
}

// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}

// import {Consumer} from 'app.js'
class ThemedButton extends React.Component {
render() {
<Consumer>
{//Consumer容器,可以拿到上文传递下来的value属性,并可以展示对应的值
(value)=><Button theme={value} />;

}
}
</Consumer>
}
  • 值得一提的是每当Provider的值发生改变时, 作为Provider后代的所有Consumers都会重新渲染。为了防止 consumers 组件中触发意外的渲染,将 value 状态提升到父节点的 state 里,除非通过setState改变value在内存中的指向,否则不会触发consumer中组件的渲染

  • 本质就是在最外层的组件上,通过生产者Provider组件进行包裹,并存储共享数据到value中,当然可以是任何数据类型。后带需要用到共享数据的组件均可通过Consumer进行数据获取。

15) Fragments

  • React 中的一个常见模式是一个组件返回多个元素。常用的解决办法无非两种:

Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
render() {
return (
// 使用显式 <React.Fragment> 语法声明的片段可能具有 key

// key 是唯一可以传递给 Fragment 的属性

<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);

// 简写成,它不支持 key 或属性
<>
<ChildA />
<ChildB />
<ChildC />
</>
}

返回一个数组

1
2
3
4
5
6
7
8
9
10
11
12
render() {
return (
{
[
<ChildA />
<ChildB />
<ChildC />
]
}

);
}
  • 16)hook介绍

  • Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

  • Hook 不能在 class 组件中使用 —— 这使得你不使用 class 也能使用 React。

  • Hook 全家福

1
2
3
4
5
6
7
8
9
10
11
12
13
1)Basic Hooks
useState
useEffect
useContext
2)自定义 Hook
3)Additional Hooks
useReducer
useCallback
useMemo
useRef
useImperativeHandle
useLayoutEffect
useDebugValue
  • Hook 使用规则

Hook 就是 JavaScript 函数,但是使用它们会有两个额外的规则:

1) 只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

2) 只能在 React 的函数组件中调用Hook。不要在其他 JavaScript 函数中调用(自定义 Hook 除外)。

1
2
3
4
5
6
7
8
9
10
11
12
13
 //ESLint 插件 可以用来强制执行这两条规则 npm install eslint-plugin-react-hooks
// 你的 ESLint 配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error", // 检查 Hook 的规则
"react-hooks/exhaustive-deps": "warn" // 检查 effect 的依赖
}
}
State Hook

useState 就是一个 Hook,通过在函数组件里调用它来给组件添加一些内部 state

更新状态的函数就类似 class 组件的 setState方法, 但是更新新状态的函数不会将新的 state 和旧的 state 合并, 而是直接替换旧的 state。

useState 会返回一对值:当前状态一个让你更新它的函数,是作为 state 的初始值, 只在第一次渲染的时候用到。可以是 number, boolean, string, object 的值, 如果初始值需要而另外的计算也可以是一个 function

出参 :[stateName, setStateFun] stateName => 当前的状态; setStateFun => 更改这个状态的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import React, { useState } from 'react';

function Example() {
// 声明一个叫 “count” 的 state 变量。初始值是0
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}> // 使用setCount更新count
Click me
</button>
</div>
);
}
Effect Hook
  • 你之前可能已经在 React 组件中执行过数据获取、订阅或者手动修改过 DOM。我们统一把这些操作称为“副作用”,或者简称为“作用”。

  • useEffect 就是一个 Effect Hook,给函数组件增加了操作副作用的能力

  • 跟 class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount 具有相同的用途,只不过被合并成了一个 API

  • 调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行你的“副作用”函数。默认情况下,React 会在每次渲染后调用副作用函数 —— 包括第一次渲染的时候

1
2
3
4
useEffect(()=>{
... // 要做的事
return () => {} // 清除操作
}, [依赖] )
  • 为什么要在 effect 中返回一个函数?

这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。

  • 通过跳过 Effect 进行性能优化

如果你传入了一个空数组([]),effect 内部的 props 和 state 就会一直拥有其初始值。尽管传入 [] 作为第二个参数更接近大家更熟悉的 componentDidMount 和 componentWillUnmount 思维模式,但我们有更好的来避免过于频繁的重复调用 effect。

1
2
3
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
自定义 Hook
  • 通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

  • 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。

参考连接

React setState 整理总结

快速入门React

React系列——React Context

React Context(上下文) 作用和使用

react 事件处理

ReactV16.3即将更改的生命周期

React之PureComponent

React中PureComponent原理

React中useEffect使用

vue使用slot分发内容与react使用prop分发内容

React Hooks

初到贵宝地,有钱的给个钱场,没钱的挤一挤给个钱场