在 react 中
UI = f(data)
,这是 React 的核心理念之一。
一个 React 组件就是一个 JavaScript 函数,并且你可以在函数中书写 markup——主要指的是 HTML 等 “标记语言” 代码。React 的 markup 类似 Vue 的 vnode 对象。
React 组件官方推荐采用 JSX 语法,具体请看:在 React 中使用 JSX。
React 内置组件的使用格式:React.<内置组件>
。
React 内置组件包括:
<>>
PureComponent 组件是基于 Component 组件实现的,是对 Component 组件的性能优化。
通过 extends PureComponent 组件能够创建一个 class 组件。
Component 组件 和 PureComponent 组件的差异:
建议在编写 class 组件时,直接继承 PureComponent 组件即可。
例如:
class ChildComponent extends React.PureComponent {
render() {
return(
<div>{this.props.numbers}</div>
)
}
}
Fragments 组件允许你将子列表分组,而无需向 DOM 添加额外节点。
例如:
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
在不需要设置 key 属性的情况下,
组件可以简写为 <>>
:
render() {
return (
<>
{condition ? (
<ChildA />
) : (
<ChildB />
)}
</>
);
}
当需要给 Fragments 标签添加 key 属性时,不能使用简短的“空标签”语法,必须显示的使用 React.Fragments:
render() {
<div>
{arr.map((item, idx) => {
return (
<React.Fragment key={idx}>
{condition ? (
<ChildA />
) : (
<ChildB />
)}
</React.Fragment>
);
})
</div>
}
function 组件是创建 React 组件的最简单方式。
例如:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
通过以下 5 步将 function 组件转成 class 组件:
React 的 class 组件使用的是 ES6 的 class 语法,通过 extends React 的 PureComponent 或 Component 组件来定义的。
例如:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
若显示声明了 constructor 方法,则必须要调用super;
在组件的非 constructor 方法中 使用 props 时,可不用传入,直接使用;
在 constructor 内使用 props ,则必须要将 props 传入 super 中。
以上说法,在ES7之后是无效的。(待研究)
ES6 中新增了类的概念,一个类必须要有 constructor 方法,如果在类中没有显示定义,则一个空的 constructor 方法会被默认添加;
一般需要在构造函数中初始化state和绑定事件,因此当需要初始化state或绑定事件时,需要显示定义 constructor 方法,并在 constructor 方法中初始化 state 和绑定事件。
例如:
import React from "react";
export default class MyComponent extends React.Component {
construction() {
super();
this.state = {
name: "Mike"
};
}
render() {
return (
<div>{this.state.name}</div>
)
}
}
ES7+ 简化了上述写法,规定 class 中可以不实现 constructor 方法,state 这样定义(据说会有一些问题,请参阅此文):
import { PureComponent } from "react";
export default class MyComponent extends PureComponent {
state = {
name: "Mike"
};
render() {
return (
<div>{this.state.name}</div>
)
}
}
一般需要在构造函数中绑定事件,但需要使用 bind,如果不想调用 bind,也可以使用箭头函数来定义函数(箭头函数不会改变 this 指向)。
举例说明:
DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Reacttitle>
head>
<body>
<div id="example">div>
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js">script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js">script>
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js">script>
<script type="text/babel">
class Apple extends React.Component {
constructor(props){
super(props);
this.state = {
name: '张三'
}
}
handlerClick(){
console.log(this.state.name);
}
render () {
return (
<button onClick = { this.handlerClick }>点我呀</button>
)
}
}
ReactDOM.render(
<div>
<Apple />
</div>,
document.getElementById("example")
);
script>
body>
html>
运行上述代码是会报错的。解决办法有两种:
方式一:直接在 constructor 里使用 bind 绑定 this
constructor(props){
super(props);
this.state = {
name: '张三'
}
this.handlerClick=this.handlerClick.bind(this);
}
handlerClick(){
console.log(this.state.name);
}
render () {
return (
<button onClick = { this.handlerClick }>点我呀</button>
)
}
但是这样又一个弊端:该方法会在页面第一次渲染时被执行一次。 可以跟“方式二”对比一下。
方式二:在 render 函数里调用方法时绑定 this
handlerClick(){
console.log(this.state.name);
}
render () {
return (
<button onClick = { this.handlerClick.bind(this) }>点我呀</button>
)
}
方式一:直接在 render 函数外用箭头函数定义方法——使用 class fields 绑定回调函数
handlerClick = () => {
console.log(this.state.name);
}
render () {
return (
<button onClick = { this.handlerClick }>点我呀</button>
)
}
方式二:直接在 render 函数里用箭头函数定义并使用方法——在回调中使用箭头函数
render () {
return (
<button onClick = { () => {
console.log(this.state.name);
}}>点我呀</button>
)
}
【注意】若采用在回调中使用箭头函数,当回调函数作为一个属性值传入低阶组件,上述这种方法可能会进行额外的重新渲染。建议使用 class fields 绑定回调函数 来避免这类性能问题。
React 组件的生命周期
React 官方:组件状态
Props vs State
ReactJS: Props vs. State
props 对象和 state 对象都是用来保存信息的,这些信息可以控制组件的渲染输出。当 props 和 state 的任一个发生变化都会触发组件的渲染更新。我们只需要通过更新该组件的 state 和其子组件的 props 就能重新渲染用户界面,尽量避免直接操作真实 DOM。
props 对象和 state 对象的一个重要的不同点就是:
因为 React 组件就是 JavaScript 函数,所以 props 对象可以理解为“函数的形参”。
React 里任何组件都不能修改自身的 props 对象——像这样的函数被称为“纯函数”——该函数不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
function HelloMessage(props) {
return <h1>Hello {props.name}!</h1>;
}
const element = <HelloMessage name="hello"/>;
ReactDOM.render(
element,
document.getElementById('example')
);
通过组件类的 defaultProps 属性为 props 设置默认值。
class Welcome extends React.Component {
render() {
return <h1>Hello {this.props.name}</h1>;
}
}
Welcome.defaultProps = {
name: "world",
};
你可以借助第三方的 Flow 或 TypeScript 等 JavaScript 扩展来对整个应用程序做类型检查,也可以直接使用 React 内置的 PropTypes 功能来做 props 的类型检查。这里只看 PropTypes。
React.PropTypes 在 React v15.5 版本后已经移到了 prop-types 库。
react 使用 PropTypes 进行类型检查
React 官方提供了 PropTypes 使用不同验证器的例子
React 支持使用 propTypes 对 props 进行验证。它可以保证我们的应用组件被正确使用。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。
首先要安装 prop-types 包:
npm i -S prop-types
然后使用 prop-types:
import PropTypes from 'prop-types';
let title = "qwert";
// let title = 123;
class MyTitle extends React.Component {
render() {
return (
<h1>Hello, {this.props.title}</h1>
);
}
}
MyTitle.propTypes = {
title: PropTypes.string
};
state 是私有的,并且完全受控于当前组件。
构造函数(constructor)是唯一可以给 this.state 赋值的地方。之后,不要直接修改 State 对象,而是应该使用 setState() 方法来更新 state 对象(具体请看下文的 React 组件的 API 部分)。
当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state 对象。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
给 setState 传递一个函数,而不是一个对象,就可以确保每次的调用都是使用最新版的 state。(具体请看下文的 React 组件的 API 部分)
【推荐文章】:
React 官方:组件状态
Props vs State
ReactJS: Props vs. State
React 组件 API
React 官方:组件状态
setState 方法是 React 事件处理函数 和 请求回调函数 中触发 UI 更新的主要方法。
setState 方法可以接收 2 个参数:
默认情况下,setState() 总是会触发一次组件重绘,除非在 shouldComponentUpdate() 钩子函数中实现了自定义一些条件渲染逻辑。
因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。否则可能会导致无法更新。要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。传递一个函数可以让你在函数内访问到当前最新的 state 的值。
例如:
class Counter extends React.Component{
constructor(props) {
super(props);
this.state = {clickCount: 0};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// setState 接收一个函数而不是一个对象作为参数
this.setState(function(state) {
return {clickCount: state.clickCount + 1};
});
}
render () {
return (<h2 onClick={this.handleClick}>点我!点击次数为: {this.state.clickCount}</h2>);
}
}
ReactDOM.render(
<Counter />,
document.getElementById('example')
);
replaceState 方法与 setState 类似,但是方法只会保留 nextState 中状态,原 state 不在 nextState 中的状态都会被删除。
replaceState 方法接收 2 个参数:
设置组件属性,并重新渲染组件。
当我们需要向组件传递数据或通知React.render()组件需要重新渲染,可以使用 setProps() 方法。
setProps 方法接收 2 个参数:
replaceProps 方法与 setProps 方法类似,但它会删除原有 props。
replaceProps 方法接收 2 个参数:
forceUpdate 方法会使组件调用自身的 render() 函数重新渲染组件,组件的子组件也会调用自己的 render() 函数。但是,组件重新渲染时,依然会读取 this.props 和 this.state,如果状态没有改变,那么 React 只会更新 DOM。
forceUpdate 方法接收 1 个参数:
forceUpdate 方法适用于 this.props 和 this.state 之外的组件重绘(如:修改了this.state后),通过该方法通知 React 需要调用 render() 函数。
一般来说,应该尽量避免使用 forceUpdate 方法,而仅从 this.props 和 this.state 中读取状态并由 React 触发 render() 函数的调用。
【拓展】更新 React 组件的方式:
一般的,react 组件间的通信是通过 props
属性 自上而下(由父及子) 进行传递的。
React 跨组件通信,一般有两种实现方式:
在 React 中 props 对象可以用来实现父组件向子组件通信。
例如:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
React 官方:Context
Context 提供了一种在组件之间共享“应用程序中许多组件都需要的属性”的方式——将这个 “多个组件共享一个全局数据” 放在 Context 对象上。而不必显式地通过组件树的逐层传递 props。
【注意】
请谨慎使用 Context,因为这会使得组件的复用性变差。
如果你只是想避免层层传递一些属性,组件组合 有时候是一个比 context 更好的解决方案。
创建一个 Context 对象。
const MyContext = React.createContext(defaultValue);
createContext 函数:
每个 Context 对象都会返回一个 Provider 组件,它允许 Consumer 组件订阅 context 的变化。
const MyContext = () => {
return(
<MyContext.Provider value={/* 某个值 */}>
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
</MyContext.Provider>
)
}
Provider 组件:
当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
Consumer 组件:
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
import React from "react";
const MyContext = React.createContext("66");
export default class ReactContext extends React.Component {
render() {
return (
<MyContext.Provider value={"999"}>
<Text />
</MyContext.Provider>
)
}
}
function Text() {
return (
<MyContext.Consumer>
{value => <p>{value}</p>}
</MyContext.Consumer>
)
}
动态切换语言。
import React from "react";
const en = {
submit: "Submit",
cancel: "Cancel"
}
const cn = {
submit: "提交",
cancel: "取消"
}
const MyContext = React.createContext(en);
class DynamicContext extends React.Component {
state = { local: cn };
toggleLocal = () => {
const local = this.state.local === en ? cn : en;
this.setState({ local });
}
render() {
return (
<MyContext.Provider value={this.state.local}>
<button onClick={this.toggleLocal}>切换语言</button>
{this.props.children}
</MyContext.Provider>
)
}
}
class LocalBtton extends React.Component {
render() {
return (
<MyContext.Consumer>
{opt => ( // 函数作为子组件
<div>
<button>{opt.cancel}</button> <button>{opt.submit}</button>
</div>
)}
</MyContext.Consumer>
)
}
}
export default () => (
<DynamicContext>
<LocalBtton />
</DynamicContext>
);
当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。例如:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
解决办法:将 value 状态提升到父节点的 state 里。例如:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<MyContext.Provider value={this.state.value}>
<Toolbar />
</MyContext.Provider>
);
}
}
组件组合:react 官方建议组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中。(将函数作为子类的组件,参见下文“高阶组件”部分)
例如:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
import FancyBorder from './FancyBorder';
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
您可以将 ref 指向任何值。但是,ref 最常见的用例是访问 DOM 元素,请:参见这篇文章。
参考:高阶组件
高阶组件 和 函数作为子组件 提供了常规组件复用之外的新的模式去使用组件。
高阶组件是:参数为组件,返回值为新组件的函数。
React 的高阶组件可以看作是一个工厂函数,接收一个组件作为参数并返回一个具有新功能或特性的组件。
举个例子:
import React from "react";
import magicBox from "./magicBox"
class MyComponent extends React.Component{
render() {
const { name } = this.props;
return (
<div>{name}</div>
);
}
}
export default magicBox(MyComponent);
React 高阶组件的实现:
// ./magicBox/index.jsx
import { PureComponent } from "react";
export default function magicBox (WrappedComponent) {
return class extends PureComponent {
state = {
name: "Mike"
};
render() {
return (
<WrappedComponent name={this.state.name} {...this.props}/>
);
}
}
}
组件与高阶组件的区别:
高阶组件的优势:
使用高阶组件的注意事项:
【注意】
React15.3中新加了一个 PureComponent 类。PureComponent
类 与 React.Component
类 都可以用来作为继承的基类,不同之处在于,PureComponent 类功能更强大,它可以减少不必要的 render 渲染次数。
参考:
React 教程:函数作为子组件(Function as Child Components)
将函数作为子组件的组件
“将函数作为子类的组件”是接收一个函数当作子组件的组件。
其原理是:利用了 React 组件的一个内置 children 属性,将函数作为子类的组件来实现。
这得益于 react 的 prop-types 的支持。
举个例子:
import { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
return (
<div>
{this.props.children('Scuba Steve')}
</div>
);
}
}
MyComponent.propTypes = {
children: React.PropTypes.func.isRequired,
};
【注意】
从 React v15.5 开始 ,React.PropTypes
助手函数已被弃用,建议使用 prop-types
库来定义 contextTypes,即你需要手动引入 import PropTypes from 'prop-types';
。
上述代码可以改为:
import { PureComponent } from 'react';
import PropTypes from 'prop-types';
class MyComponent extends PureComponent {
render() {
return (
<div>
{this.props.children('Scuba Steve')}
</div>
);
}
}
MyComponent.propTypes = {
children: PropTypes.func.isRequired,
};
由上述代码可知:通过函数创建子类组件的组件,可以将 父类组件 和 它们的子类组件 解耦,让设计者决定选用哪些参数,及怎么将参数应用于子类组件。
使用者可能考虑以不同的方式使用该组件,比如:
<MyComponent>
{(name) => (
<div>{name}div>
)}
MyComponent>
<MyComponent>
{(name) => (
<img src=’/scuba-steves-picture.jpg’ alt={name} />
)}
MyComponent>
将函数作为子类的组件的优势:
将函数作为子类的组件 与 react 高阶组件 的比较:
MyComponent.SomeContant = ‘SCUBA’;
,然后由高阶组件包裹,export default connect(...., MyComponent);
。你的常数就好像死了。如果没有高阶组件提供访问底层组件类的函数,则再也访问不到它。【参考文章】:
React 官网
React 中文文档