Yapei Li

专注于前端领域

0%

React16

主要概念

1、 在常规的 React 数据流中,props 是父组件与子组件交互的唯一方式,要修改子元素,需要用新的 props 去重新渲染子元素

2、 函数组件必须显式传递props,类组件不需要显式传递但是获取时需要在前边加this(this.props)

3、 提取组件的规则:(1)会被复用的组件要提出来;(2) 当条件渲染的条件过于复杂时;(3) 当map()中有太多嵌套时

4、 props是只读的,无论是函数组件还是类组件,都无法修改自身的props

5、 state是私有的,并且由组件自身控制

6、 存储不参与数据流的内容(如计时器id)可以自由向该类(组件类本身不是state)中添加其他字段(不用声明变量,直接通过this.xxx=xxx 添加)

7、 setState用法:

(1) this.setState({xxx:yyy})

(2) this.setState((state,props)=>({xxx:yyy})),该函数接收前一个状态值(变化前的state)作为第 1 个参数, 并将更新后的props值作为第 2 个参数:

8、 无论作为父组件还是子组件,它都无法获悉另一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。state不能被自身组件以外的任何组件访问

9、 单向数据流:任何state始终由某个特定的组件所有,并且从该state导出的任何数据或ui只能影响树中“下方”的组件

10、 React中不能通过 return false 来阻止默认行为,必须明确调用e.preventDefault()

事件冒泡处理: 1、合成事件间,e.stopPropagation();2、原生事件与最外层document上的事件间,e.nativeEvent.stopImmediatePropagation();3、阻止合成事件除最外层document上的原生事件,’判断e.target

11、 函数组件获取事件及props前边不能加this

12、 利用箭头函数传递事件,必须显式的传递事件对象e

13、 如果函数需要通过props传递给下级组件以便下级组件调用,建议使用在构造函数中.bind(this)的方法绑定this

14、 属性初始化语法:不用bind(this),(构造函数外)xxx=()=>{console.log(this)}

15、 在回调中使用一个箭头函数(不用绑定this,但是必须显式的传递事件对象e

1
<div onClick={(e)=>{ this.ccc(e)}}></div>

16、 array.map((item,index)=>{})列表渲染,“key”是必须带有的特殊字符串属性,只要调用map()须绑定keys

17、 键(keys)是react的一个内部映射,不会传递到组件内部(组件内部获取不到keys

18、 在React中,可变状态一般保存在组件的state中,并且只能通过setState()更新

19、 “受控组件”:其值由React控制的输入表单元素

20、 任何可变数据都应遵循"单一数据源"的原则

21、 能从props获取的数据不建议从state获取

22、 将父组件的函数通过props传递给子级,子级通过this.props.xxx就可以调用父级的函数

23、 组件JSX标签中的任何内容(包括直接写的文字)都会被传递到组件中,作为children prop通过{props.children}获取

24、 组件JSX标签中的内容,在组件中不使用{props.children}接收时,是不显示的

25、 组件可以接收任意的props,可以是原始值、React元素、函数

26、 Refs:强制修改子元素。被修改的子元素可以是React组件实例(类组件的实例),或者是一个DOM元素,不能是函数组件

27、 使用React.createRef()创建refs,通过ref属性来获得子级元素,使用ref.current属性对节点进行访问

28、 不受控组件初始值设置:使用defaultValue而不是value属性,单选与多选支持defaultCheckedselecttextarea支持defaultValue

29、 状态提升:同一个数据的变化需要几个不同的组件来反映时,需要提升这个共享的状态到用到它的组件的最近的祖先组件中

30、 状态提升主要在于子组件接收父组件的数据、子组件怎么调用父组件的函数来改变父组件的数据,明白这一点以后,就是组件的嵌套

31、 函数组件中定义函数 必须是 : function xxx(){}

32、 React 的编程思想

  • UI 拆解到组件层次结构中
  • React 构建一个静态版本
  • 确定 UI state(状态) 的最小但能完整展现功能的父级组件
  • 确定哪个组件可变,或者说哪个组件使用这些 state(状态) 。
  • 添加反向数据流(就是:子组件改变父组件数据,一般通过props将改变父组件数据的父组件函数传递给子组件)

33、 在JSX中,label 标签的 for 特性被写作htmlFor

34、 何时使用 Refs

  • 处理focus、文本选择或者媒体播放
  • 触发强制动画
  • 集成第三方DOM
  • 如果可以通过声明式实现,就尽量避免使用 refs

35、 能使用状态提升运用state改变组件状态,不要使用 refs 来更新组件

36、 当ref是类组件实例时,可以直接获取实力上的属性及方法

37、 不能在函数式组件上使用 ref 属性,因为它们没有实例,如果需要使用 ref ,就需要将组件转化成 类(class)组件,就像需要 生命周期方法 或者 state 一样。

38、 但是可以 在函数式组件内部使用 ref 来引用一个 DOM 元素或者 类(class)组件

39、 能用 createRef不用回调refs

40、 “回调 ref”不同于传递 createRef() 创建的 ref 属性,它会传递一个函数。这个函数接受 React 组件的实例或 HTML DOM 元素作为参数,以存储它们并使它们能被其他地方访问

41、 在组件间通过props传递回调形式的 refs(父组件获取子组件元素)

42、 类组件 render函数 return的只能有一个根元素

43、 回调ref不需要通过 current属性访问,直接this.textInput.focus();

44、 Ref 转发是一种自动将 ref 通过组件传递给子组件的技术

45、 React.forwardRef((props,ref)=>())接收propsref作为参数

46、 Ref 转发不限于 DOM 组件。也可以将 refs 转发给类组件实例

(1)创建 ref

this.ref=React.createRef();

(2)绑定ref

1
2
3
<FancyButton ref={this.ref}>
我是你绑定的ref
</FancyButton>

(3)ref转发

1
2
3
4
5
const FancyButton =React.forwardRef((props,ref)=>(
<button ref={ref} className="FancyButton">
{props.children}
</button>
))

47、 凡是用createRef创建的都需要使用current属性访问元素

48、 将父组件通过createRef创建的Ref通过props传递给子组件,子组件将传递过来的ref绑定在其元素上也能实现 ref转发

49、 高阶组件是一个函数,能够接受一个组件并返回一个新的组件。

50、 代码拆分: 可以帮助你 “懒加载(lazy-load)” 用户当前需要的东西,这可以显著提高应用程序的性能。

51、 Suspense:在等待加载时显示一些后备内容 - 例如加载指示符。

52、 React.lazy 目前仅支持默认导出的组件(export default ),如果想要导入的模块使用命名导出,则可以创建一个中间模块,将其重新导出为默认模块。

53、 基于路由的代码拆分:在定义路由是使用代码拆分是最好的

54、 上下文(Context) 提供了一种通过组件树传递数据的方法,无需在每个级别手动传递 props 属性。

55、 当一些数据需要在不同的嵌套级别上被许多组件访问时,首先考虑使用 Context

56、 组件是React中代码重用的最小单元

57、 从本质上讲,JSX 只是为 React.createElement(component, props, ...children) 函数提供的语法糖

58、 初始化state必须在constructor中,并且调用constructor后必须使用super(props)props 传递给基础构造函数

59、所有 React 组件都必须是纯函数,并禁止修改其自身 props

60、“纯函数” :不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果

61、唯一可以分配 (初始化state)this.state 的地方是构造函数construct

62、所有组件都是完全独立的,独立更新

63、通过 JSX , 传递一个函数作为事件处理程序而不是一个字符串(onClick={}而不是onClick="")

64、通过返回null实现显示隐藏,从组件的 render 方法返回 null 不会影响组件生命周期方法的触发

65、event.preventDefault() 阻止默认事件

create-react-app

npm 内置create-react-app 脚手架,不用特意安装create-react-app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//创建my-react-app
npx create-react-app my-app
//或者
npm init react-app my-app


//进入项目文件夹
cd my-app

//启动服务
npm start

//打包项目文件
npm run build 或者 yarn build


//在项目中展示配置项
npm run eject

JSX

JavaScrip 的一种扩展语法

从本质上讲,JSX 只是为 React.createElement(component, props, ...children) 函数提供的语法糖

{} 在JSX中嵌入表达式

用 花括号{} 可以把任意的 JavaScript 表达式 嵌入到 JSX 中。例如:2 + 2user.firstName, 和 formatName(user)

推荐使用括号将 JSX 包裹起来,可以避免分号自动插入的陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from 'react';
import ReactDOM from 'react-dom';

function formatName(user) {
return user.firstName + ' ' + user.lastName;
}

const user = {
firstName: 'Harper',
lastName: 'Perez'
};

//推荐使用括号将 JSX 包裹起来,可以避免分号自动插入的陷阱
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);

ReactDOM.render(
element,
document.getElementById('root')
);

JSX 也是一个表达式

编译之后,JSX 表达式就变成了常规的 JavaScript 对象

可以在 if 语句或者是 for 循环中使用 JSX,可以用jsx给变量赋值、当做参数接收,或者作为函数的返回值

1
2
3
4
5
6
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}

JSX 指定属性值

  1. 使用双引号来指定字符串字面量作为属性值
  2. 用花括号嵌入一个 JavaScript 表达式作为属性值
  3. 在属性中嵌入 JavaScript 表达式时,不要使用引号将花括号括起来
  4. 引号(用于字符串值)或大括号(用于表达式),不能同时使用于同一个属性
  5. react的属性名应用驼峰命名,例如:class 变成了 classNametabindex 变成了 tabIndex
1
2
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;

JSX 指定子元素

  1. 单标签必须用/>闭合

  2. JSX 标签可以像html一样直接包含子元素

1
2
3
4
5
6
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);

JSX 原理(JSX是一个对象)

JSX 原理是:Babel 编译时调用React.createElement() ,将jsx编译为React元素对象(普通json对象)

下面的两种方法是完全相同的:

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

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

上面两种最后都会是一个对象,我们称之为:**React元素**

1
2
3
4
5
6
7
8
// 注意: 这是简化的结构
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};

对 JSX 类型使用点语法( <MyComponents.DatePicker />

可以使用点语法引用一个 React 组件

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" />;
}

JSX 中的 props(属性)

JavaScript 中,if 语句和 for 循环不是表达式,因此不能在 JSX 中直接使用。但你可以将他们放在附近的代码块中

1
2
3
4
5
6
7
8
9
function NumberDescriber(props) {
let description;
if (props.number % 2 == 0) {
description = <strong>even</strong>;
} else {
description = <i>odd</i>;
}
return <div>{props.number} is an {description} number</div>;
}

props(属性) 默认为 “true”

不建议使用这种写法

如果你没给 prop(属性) 传值,那么他默认为 true 。下面两个 JSX 表达式是等价的:

1
2
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />

属性展开

如果有一个 object 类型的 props,并且希望在 JSX 中传入,可以使用扩展操作符 ... 传入整个 props 对象。这两个组件是等效的:

1
2
3
4
5
6
7
8
function App1() {
return <Greeting firstName="Ben" lastName="Hector" />;
}

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

也可以挑选组件需要的的指定属性(props),同时使用展开运算符传递所有其他属性(props)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const Button = props => {
const { kind, ...other } = props;
const className = kind === "primary" ? "PrimaryButton" : "SecondaryButton";
return <button className={className} {...other} />;
};

const App = () => {
return (
<div>
<Button kind="primary" onClick={() => console.log("clicked!")}>
Hello World!
</Button>
</div>
);
};

JSX 中的 Children

字符串字面量

开放标签和闭合标签中放入一个字符串,那么 props.children 就是那个字符串

1
2
<MyComponent>Hello world!</MyComponent>
//MyComponent 组件中的 props.children 值为字符串 "Hello world!"

JSX会删除每行开头和结尾的空格,并且也会删除空行。邻接标签的空行也会被移除,字符串之间的空格会被压缩成一个空格

下面写法效果一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>Hello World</div>

<div>
Hello World
</div>

<div>
Hello
World
</div>

<div>

Hello World
</div>

JavaScript 表达式作为 Children(子元素)`

通过使用 {} 包裹,你可以将任何的 JavaScript 元素而作为 children(子元素) 传递

1
2
3
4
5
6
7
8
9
10
11
12
function Item(props) {
return <li>{props.message}</li>;
}

function TodoList() {
const todos = ['finish doc', 'submit pr', 'nag dan to review'];
return (
<ul>
{todos.map((message) => <Item key={message} message={message} />)}
</ul>
);
}
1
2
3
function Hello(props) {
return <div>Hello {props.addressee}!</div>;
}

Functions(函数) 作为 Children(子元素)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Calls the children callback numTimes to produce a repeated component
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}

function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}

Booleans, Null, 和 Undefined 被忽略

false,null,undefined,和 true 都是有效的的 children(子元素) 。但是并不会被渲染

在输出中想要渲染 falsetruenull 或者 undefined ,必须先将其转化为字符串

下面的JSX表达式渲染效果是相同的:

1
2
3
4
5
6
7
8
9
10
11
<div />

<div></div>

<div>{false}</div>

<div>{null}</div>

<div>{undefined}</div>

<div>{true}</div>

需要注意的是“false”值,例如数值 0 ,仍然会被 React 渲染。例如,这段代码不会按照你预期的发生,因为当 props.messages 是一个空数组时 0 会被打印

1
2
3
4
5
6
7
<div>
{props.messages.length &&
<MessageList messages={props.messages} />
}
</div>

<!-- length 是0 时 渲染 0 -->

确保 && 之前的表达式总是布尔值

1
2
3
4
5
<div>
{props.messages.length > 0 &&
<MessageList messages={props.messages} />
}
</div>

页面中引用本地图片、背景图设置

方法一:

1
<img src={require('../img/icon1.png')} alt="" />

方法二:

1
2
3
4
import search from '../img/search.png'
import user from '../img/user.png'
<img src={search} alt="" />
<img src={user} alt="" />

方法三:(不推荐使用)

1
<img src='http://localhost:3000/my-project/src/img/icon1.png' alt="" />

背景图片引用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const divStyle = {
color: 'red',
backgroundImage: 'url(' + imgUrl + ')',
// 或者 background: `url${require("1.jpg")}`
};

function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}

//2
import log from './logo.svg'
var divStyle={
width:'100px',
height:'100px',
color:'red',
background:`url(${log})`
}
const element=(
<div style={divStyle}></div>
)

元素渲染(ReactDOM.render())

不同于浏览器的 DOM 元素, React 元素是普通的对象React DOM 会负责更新 DOM ,以匹配React元素(使DOM元素与React元素保持一致)

1
2
3
4
5
6
7
8
9
10
11
//jsx 创建的 react元素
const element = (
<h1>Hello, world</h1>
);

ReactDOM.render(
//react元素
element,
//react元素挂在位置
document.getElementById('root')
);

更新已渲染的元素

React 元素是 不可突变(immutable) 的. 一旦你创建了一个元素, 就不能再修改其子元素或任何属性

更新 UI 的唯一方法是:创建一个新的元素, 并将其传入 ReactDOM.render() 方法.

大多数 React 应用只会调用 ReactDOM.render() 一次,有状态的类组件会解决更新dom问题

React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。

1
<div id="root">时钟渲染更新元素:</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//时钟渲染更新元素
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
//进入页面先渲染
tick();
//每1s更新dom
setInterval(tick, 1000);

组件(Components) 和 属性(Props)

组件就像JavaScript的函数,组件可以接收任意输入属性(称为”props”)

Props , 即属性(Property),写法就像html标签的属性

组件名称必须以大写字母开头<div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件

函数式组件,必须显式的传递props

1
2
3
4
5
6
7
8
9
10
11
//定义
function Welcome(props) {
//props就是使用组件时加入的 属性 的集合
return <h1>Hello, {props.name}</h1>;
}

//调用 加入属性
<Welcome name="aaa" />

//渲染结果
<h1>Hello, aaa</h1>

类组件,不用显式的传递props

获取props的时候前边必须加thisthis.props

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//定义
//extends ES6 类的继承
//Welcome类 继承 React.Component
class Welcome extends React.Component {
//写不写constructor都能直接使用this.props获取属性
constructor(props){
super(props);
}
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}

//使用
<Welcome name="aaa" />

//渲染结果
<h1>Hello, aaa</h1>

渲染一个组件

React 遇到一个代表用户定义组件的元素时,它将 JSX 中的属性以一个单独对象的形式传递给相应的组件。 我们将其称为 “props” 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
//或者
ReactDOM.render(
<Welcome name="Sara" />,
document.getElementById('root')
);

上面这个例子的渲染步骤:

  1. 我们调用了 ReactDOM.render() 方法并向其中传入了 <Welcome name="Sara" /> 元素。
  2. React 调用 Welcome 组件,并向其中传入了 {name: ‘Sara’} 作为 props 对象。
  3. Welcome 组件返回 <h1>Hello, Sara</h1>
  4. React DOM 迅速更新 DOM ,使其显示为 <h1>Hello, Sara</h1>

组件嵌套

组件可以在它们的输出中引用其它组件

提取组件的规则:会被复用的组件要提取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}

function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}

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

Props 是只读的

无论用函数或类的方法来声明组件, 它都无法修改其自身 props

所有 React 组件都必须是纯函数,并禁止修改其自身 props

“纯函数” :不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果

1
2
3
function sum(a, b) {
return a + b;
}

非纯函数:改变了自身的输入值

1
2
3
function withdraw(account, amount) {
account.total -= amount;
}

状态(State) 和 生命周期

<a href="#a1">时钟渲染更新元素 </a> 组件提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//时钟渲染更新元素  组件提取
function Clock(props) {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {props.date.toLocaleTimeString()}.</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()} />,
document.getElementById('root')
);
}

setInterval(tick, 1000);

理论上上边的例子:我们只需要引用一个 Clock , 不需要每次都调用ReactDOM.render函数它就可以自动计时并更新:

1
2
3
4
ReactDOM.render(
<Clock />,
document.getElementById('root')
);

要实现这点,我们就需要用到 state

stateprops 类似,但是state是私有的,并且由组件本身完全控制。

类定义的组件有一些额外的特性。 这个”类专有的特性”, 指的就是局部状态 state

把函数式组件转化为类组件

  1. 创建一个继承自 React.Component 类的 ES6 class 同名类。

  2. 添加一个名为 render() 的空方法。

  3. 把原函数中的所有内容移至 render() 中。

  4. 在 render() 方法中使用 this.props 替代 props

  5. 删除保留的空函数声明。

把Clock组件改写为类组件:

每次更新发生时(state发生变化时)都会调用 render 方法,只要我们将 <Clock /> 渲染到同一个 DOM 节点中,就只会使用 Clock 类的单例(Clock 类的实例)这让我们可以使用额外功能,例如本地状态(state)生命周期方法

1
2
3
4
5
6
7
8
9
10
class Clock extends React.Component{
render(){
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}

在类组件中添加本地状态(state)

初始化state必须在constructor中,并且调用constructor后必须使用super(props)将 props 传递给基础构造函数;

过以下3步, 把date从属性(props) 改为 状态(state):

  1. 替换 render() 方法中的 this.props.date 为 this.state.date:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    class Clock extends React.Component {
    render() {
    return (
    <div>
    <h1>Hello, world!</h1>
    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
    );
    }
    }
  2. 添加一个 类构造函数(constructor) 初始化 this.state:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Clock extends React.Component {
    constructor(props) {
    super(props);
    this.state = {date: new Date()};
    }
    render() {
    return (
    <div>
    <h1>Hello, world!</h1>
    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
    );
    }
    }
注意我们如何将 props 传递给基础构造函数(super(props)):

类组件应始终使用 props 调用基础构造函数

1
2
3
4
constructor(props) {
super(props);
this.state = {date: new Date()};
}
  1. 移除<Clock />元素中的 date 属性:
    1
    2
    3
    4
    5
    ReactDOM.render(
    //原本 <Clock date={new Date()}/>
    <Clock />,
    document.getElementById('root')
    );
  2. 结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class Clock extends React.Component {
    constructor(props) {
    super(props);
    this.state = {date: new Date()};
    }

    render() {
    return (
    <div>
    <h1>Hello, world!</h1>
    //直接使用 {this.state.date} 会报错
    <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
    </div>
    );
    }
    }

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

在类组件中添加生命周期方法

  1. 当 Clock 第一次“挂载(mounting)” 时设置一个定时器。

  2. 当 Clock “卸载(unmounting)销毁”之前 ,清除定时器。

this.props 由 React 本身设定, 而 this.state 具有特殊的含义,如果需要存储不参与数据流的内容(如计时器id)可以自由向该类(组件类本身不是state)中添加其他字段(不用声明变量,直接通过this.xxx 添加)

通过 setState(), React 得知了组件 state(状态)的变化, 随即再次调用 render() 方法,获取了当前应该显示的内容。

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
class Clock extends React.Component{
//使用 props 调用基础构造函数
constructor (props){
super(props)
//加入state
this.state={date:new Date()}
}
//挂载生命周期钩子函数
componentDidMount(){
this.timer=setInterval(()=>this.tick(),1000)
}
//卸载、销毁之前生命周期钩子函数
componentWillUnmount(){
clearInterval(this.timer)
}
//定义函数
//使用 this.setState() 来来周期性地更新组件本地状态
tick(){
this.setState({
date:new Date()
})
}
//导出组件
render(){
return (
<div>
<h1>Hello , 甘草倚半夏</h1>
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
</div>
)
}
}

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

不能直接修改 state(状态),使用this.setState() 改变state

唯一可以分配 (初始化state)this.state 的地方是构造函数construct

1
2
3
4
5
// 错误
this.state.comment = 'Hello';

// 正确
this.setState({comment: 'Hello'});

state(状态) 更新可能是异步的,setState((state, props) => ({}))

React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新

因为this.props 和 this.state 可能是异步更新的,所以不能依赖他们的值计算下一个state(状态)

1
2
3
4
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});

解决办法:

利用setState接收一个函数的形式,setState((state, props) => ({}))

该函数接收前一个状态值(变化前的state)作为第 1 个参数, 并将更新后的props值作为第 2 个参数:

1
2
3
4
5
6
7
8
9
10
11
// 正确  箭头函数
this.setState((state, props) => ({
counter: state.counter + props.increment
}));

// 正确 常规的函数也可以
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});

state(状态)更新会被合并

合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换this.state.comments 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}

componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});

fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}

数据向下流动 (“单向”数据流)

state(状态) 被称为 本地状态 或 封装状态的原因:无论作为父组件还是子组件,它都无法获悉另一个组件是否有状态,同时也不需要关心另一个组件是定义为函数组件还是类组件。

state不能被自身组件以外的任何组件访问

组件可以选择将 state(状态) 向下传递,作为其子组件的 props(属性):

1
2
3
4
5
6
7
8
9
10
//原生htyml标签
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
//自定义组件
<FormattedDate date={this.state.date} />
//FormattedDate 组件通过 props(属性) 接收了 date 的值,但它仍然不能获知该值是来自于 Clock的 state(状态) ,还是 Clock 的 props(属性),或者是直接手动创建的

function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

“单向”的数据流

任何 state(状态) 始终由某个特定组件所有,并且从该 state(状态) 导出的任何数据 或 UI 只能影响树中 “下方” 的组件

所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动

所有组件都是完全独立的,独立更新

数据向下示例:

父级state作为子级的props传向子级

父级state变化时子级不需要重新渲染就会跟着变化,因为:子组件存在于父组件的render中

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
40
41
class H2 extends React.Component{
render(){
return (
<h2>It is {this.props.date}</h2>
)
}
}
class Clock extends React.Component{
constructor (props){
super(props)
this.state={date:new Date()}
}
componentDidMount(){
this.timer=setInterval(()=>this.tick(),1000)
}
componentWillUnmount(){
clearInterval(this.timer)
}
tick(){
this.setState({
date:new Date()
})
}

render(){
return (
<div>
<h1>Hello , 甘草倚半夏</h1>
/*
*<h2>It is {this.state.date.toLocaleTimeString()}</h2>
*/
<H2 date={this.state.date.toLocaleTimeString()}></H2>
</div>
)
}
}

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

处理事件

  1. React 事件使用驼峰命名,而不是全部小写。

  2. 通过 JSX , 传递一个函数作为事件处理程序而不是一个字符串(onClick={}而不是onClick="")。

  3. React 中不能通过返回 false(即 return false; ) 来阻止默认行为。必须明确调用 e.preventDefault

  4. React中的e 是一个合成的事件事件对象,不需要担心跨浏览器的兼容性问题

  5. 不需要调用 addEventListenerDOM 元素被创建后添加事件监听器。只要当元素被初始渲染的时候提供一个监听器就可以了

  6. 使用ES6 类 定义一个组件时,一个事件处理程序就是类上的一个方法

  7. 绑定事件时onClick={this.youfunction}中的事件youfunction后面不能带()不然会报错 ,但是加一层箭头函数就能带()onClick={()=>{this.youfunction()}}

  8. 函数组件中定义函数 必须是 : function xxx(){}

  9. 函数组件获取事件props不能带this

1
2
3
4
5
6
7
8
//原生HTML
<button onclick="activateLasers()">
Activate Lasers
</button>
//React中
<button onClick={activateLasers}>
Activate Lasers
</button>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//React中的阻止默认事件
// e 是一个合成的事件对象,不需要担心跨浏览器的兼容性问题
//函数组件获取事件及props不能带this
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}

return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}

类组件添加事件示例

  1. 定义事件

  2. 在construct构造函数中绑定this,事件函数后面的.bind(this)是必须的

  3. 在元素中使用

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
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};

//2. 在construct构造函数中绑定this
// 这个绑定是必要的,使`this`在回调中起作用
this.handleClick = this.handleClick.bind(this);
}
//1. 定义事件
handleClick() {
this.setState(state => ({
isToggleOn: !state.isToggleOn
}));
}

render() {
return (
//3. 在元素中使用
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}

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

JSX回调中必须注意 this 的指向,在 JavaScript 中,类方法默认是没有 绑定this 的。如果this.handleClick未绑定this 并赋值给了onClick事件,那么在this.handleClick函数中this 会是 undefined

定义函数时使用 属性初始化语 ,可以解决this绑定问题

利用箭头函数定义事件:不使用属性初始化语,在回调中(事件绑定中)使用一个 箭头函数 ,也可以解决this绑定问题 ,存在的问题是:每次 LoggingButton 渲染时都创建一个不同的回调,在这个回调被作为 prop(属性) 传递给下级组件,这些组件可能需要额外的重复渲染。

利用箭头函数定义事件,必须显式的传递事件对象e

如果函数需要传递给下级组件通常建议在构造函数中.bind(this)进行绑定,以避免这类问题

属性初始化语法(就是声明式箭头函数)

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 LoggingButton extends React.Component {
// 这个语法确保 `this` 绑定在 handleClick 中。
// 警告:这是 *实验性的* 语法。
handleClick = () => {
console.log('this is:', this);
}

render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}

//在回调中使用一个 箭头函数
class LoggingButton extends React.Component {
handleClick() {
console.log('this is:', this);
}

render() {
// This syntax ensures `this` is bound within handleClick
//利用箭头函数定义事件,必须显式的传递事件对象e
return (
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
);
}
}

事件函数传参

在循环内部,通常需要将一个额外的参数(比如id)传递给事件处理程序

参数 e 作为 React 事件对象将会被作为第二个参数进行传递,通过箭头函数的方式,事件对象必须显式的进行传递

  1. 普通事件(在constructor中.bind(this)的方式)传参 onClick={this.deleteRow.bind(this, id)}
  2. 箭头函数传参,显式的传递e并且e是第二个传递进函数的onClick={(e) => this.deleteRow(id, e)}
1
2
3
4
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

//上述两行代码是等价的

在函数绑定不传参数的时候,在函数体中默认第一个形参就是事件对象e,不管定义形参名字是什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class App extends React.Component{
constructor(props){
super(props)
this.clock=this.clock.bind(this)
}
//data就相当于e
clock(data){
console.log(data.clientX);
}

render(){
return <h2 onClick={this.clock}>点我试试传参</h2>
}
}
//打印的就是 鼠标点击x轴坐标

但是使用.bind(this,xxx)传递参数xxx时,形参就是传递的数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class App extends React.Component{
constructor(props){
super(props)
//这里的bind(this) 跟传参时的不冲突
this.clock=this.clock.bind(this)
}
//data 代表的是传递过来的参数
clock(data){
console.log(data);
}
render(){
return <h2 onClick={this.clock.bind(this,15)}>点我试试传参</h2>
}
}

利用箭头函数进行参数传递时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class App extends React.Component{
constructor(props){
super(props)
this.clock=this.clock.bind(this)
}
//data 是传递的数据
//e 是事件对象
clock(data,e){
console.log(data);
console.log(e)
}
render(){
return <h2 onClick={(e)=>{this.clock(15,e)}}>点我试试传参</h2>
}
}

重写bind方法使传参时不必传递this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//改写bind方法
Function.prototype.b = function(...args){
var Fn = this;
return function(e){
Fn(...args,e);
}
}

//函数定义
click=(value)=>{
dosomething...
}

//使用
onClick={this.click.b("1111")}

合成事件与原生事件的区别

1、合成事件在jsx中直接绑定的事件,如

1
<a ref="aaa" onClick={(e)=>this.handleClick(e)}>更新</a>

这里的handleClick事件就是合成事件

2、原生事件通过js原生代码绑定的事件,如

1
2
3
4
5
6
7
8
9
10
11
document.body.addEventListener('click',e=>{
// 通过e.target判断阻止冒泡
if(e.target&&e.target.matches('a')){
return;
}
console.log('body');
})
//或
this.refs.update.addEventListener('click',e=>{
console.log('update');
});

事件冒泡处理

A、合成事件间,e.stopPropagation();

阻止合成事件间的冒泡,在事件函数中添加e.stopPropagation();

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
import React,{ Component } from 'react';
import ReactDOM,{findDOMNode} from 'react-dom';

class Counter extends Component{
constructor(props){
super(props);
this.state = {
count:0,
}
}

handleClick(e){
// 阻止合成事件间的冒泡
e.stopPropagation();
this.setState({count:++this.state.count});
}

testClick(){
console.log('test')
}

render(){
return(
<div ref="test" onClick={()=>this.testClick()}>
<p>{this.state.count}</p>
<a ref="update" onClick={(e)=>this.handleClick(e)}>更新</a>
</div>
)
}
}

var div1 = document.getElementById('content');

ReactDOM.render(<Counter/>,div1,()=>{});

B、原生事件与最外层document上的事件间,e.nativeEvent.stopImmediatePropagation();

阻止原生事件与最外层document上的事件间的冒泡,用e.nativeEvent.stopImmediatePropagation();

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
import React,{ Component } from 'react';
import ReactDOM,{findDOMNode} from 'react-dom';

class Counter extends Component{
constructor(props){
super(props);
this.state = {
count:0,
}
}

handleClick(e){
// 阻止原生事件与最外层document上的事件间的冒泡
e.nativeEvent.stopImmediatePropagation();
this.setState({count:++this.state.count});
}

render(){
return(
<div ref="test">
<p>{this.state.count}</p>
<a ref="update" onClick={(e)=>this.handleClick(e)}>更新</a>
</div>
)
}

componentDidMount() {
document.addEventListener('click', () => {
console.log('document');
});
}
}

var div1 = document.getElementById('content');

ReactDOM.render(<Counter/>,div1,()=>{});

C、阻止合成事件与除最外层document上的原生事件,’判断e.target’

C、阻止合成事件与除最外层document上的原生事件间的冒泡,通过判断e.target来避免

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
import React,{ Component } from 'react';
import ReactDOM,{findDOMNode} from 'react-dom';

class Counter extends Component{
constructor(props){
super(props);
this.state = {
count:0,
}
}
handleClick(e){
this.setState({count:++this.state.count});
}
render(){
return(
<div ref="test">
<p>{this.state.count}</p>
<a ref="update" onClick={(e)=>this.handleClick(e)}>更新</a>
</div>
)
}

componentDidMount() {
document.body.addEventListener('click',e=>{
// 通过e.target判断阻止冒泡
if(e.target&&e.target.matches('a')){
return;
}
console.log('body');
})
}
}

var div1 = document.getElementById('content');

ReactDOM.render(<Counter/>,div1,()=>{});

条件渲染

React 中的条件渲染就和在 JavaScript 中的条件语句一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}

function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}

function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}

ReactDOM.render(
// 修改为 isLoggedIn={true} 试试:
<Greeting isLoggedIn={false} />,
document.getElementById('root')
);

元素变量

用变量来存储元素,不同条件下将不同组件赋值给变量,然后直接用{变量}渲染变量

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}

function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}


class LoginControl extends React.Component {
constructor(props) {
super(props);
this.handleLoginClick = this.handleLoginClick.bind(this);
this.handleLogoutClick = this.handleLogoutClick.bind(this);
this.state = {isLoggedIn: false};
}

handleLoginClick() {
this.setState({isLoggedIn: true});
}

handleLogoutClick() {
this.setState({isLoggedIn: false});
}

render() {
//定义变量
const isLoggedIn = this.state.isLoggedIn;
let button;

//将元素赋值给变量
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}

return (
<div>
{button}
</div>
);
}
}

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

使用逻辑 && 操作符的内联 if 用法

运用的是逻辑与在赋值操作中的用法

如果条件为 true ,则 && 后面的元素将显示在输出中。 如果是 false,React 将会忽略并跳过它。

1、逻辑与在条件判断中的用法:

&&:所有条件都为真,整体才为真

||:只要有一个条件为真,整体都为真

2、在赋值操作中

||: A|| B 首先看A的真假,A为真返回的是A的值,A为假返回的B的值(不管B是啥) 1|| 2 => 1 ;0|| false=>false;

&& : A&& B 首先看A的真假,A为真返回B的值,A为假返回A的值;1&&2 => 2;0&&false => 0

真实项目中我们利用逻辑或实现默认值的设置操作,形参的默认值赋值

callback && callback(); callback为真callback就执行

**逻辑与 的优先级高于 逻辑或 ** 先计算 逻辑与

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function Mailbox(props) {
const unreadMessages = props.unreadMessages;
return (
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
);
}

const messages = ['React', 'Re: React', 'Re:Re: React'];
ReactDOM.render(
<Mailbox unreadMessages={messages} />,
document.getElementById('root')
);

内联三元运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}

render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}

当条件变得太复杂时,可能是提取组件的好时机

防止组件渲染(返回null隐藏组件),react的显示隐藏

在下面的例子中,如果 prop 值为 false ,则该组件不渲染:

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
40
41
function WarningBanner(props) {
if (!props.warn) {
return null;
}

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

class Page extends React.Component {
constructor(props) {
super(props);
this.state = {showWarning: true};
this.handleToggleClick = this.handleToggleClick.bind(this);
}

handleToggleClick() {
this.setState(state => ({
showWarning: !state.showWarning
}));
}

render() {
return (
<div>
<WarningBanner warn={this.state.showWarning} />
<button onClick={this.handleToggleClick}>
{this.state.showWarning ? 'Hide' : 'Show'}
</button>
</div>
);
}
}

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

从组件的 render 方法返回 null 不会影响组件生命周期方法的触发

列表(Lists) 和 键(Keys),arr.map((item,index)=>())

利用 arr.map((item,index)=>())

创建元素列表时,“key” 是一个你需要包含的特殊字符串属性,不然会报错

键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识

key 最好使用一个在它的同辈元素中不重复的标识字符串。可以使用数据中的 ID 作为 keys,没有ID 时,可以用数据项的索引值作为 key ,如果列表项的顺序可能发生改变,不建议使用索引作为 keys

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);

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

//使用index作为key
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);

使用 keys 提取组件

key 应该放置在有数组处理的map函数中(<ListItem />) 的元素中,不能放在 ListItem 组件自身中的 <li> 根元素上

元素中只要调用 map() 就需要绑定 keys

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
function ListItem(props) {
// 正确!这里不需要指定 key :
return <li>{props.value}</li>;

/**
//错误
return (
<li key={value.toString()}>
{value}
</li>
);
**/
}

function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在这里被指定
<ListItem key={number.toString()}
value={number} />

);
return (
<ul>
{listItems}
</ul>
);
}

const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);

在数组中使用的 keys 必须在它们的同辈之间唯一。然而并不需要全局唯一。可以在操作两个不同数组的时候使用相同的 keys

键(keys)是React的一个内部映射,不会传递给组件的内部(组件内部获取不到keys)。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。

1
2
3
4
5
6
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);

上面的例子中, Post 组件可以读取 props.id,但是不能读取 props.key 。

在 JSX 中嵌入 map()

JSX允许在大括号中嵌入任何表达式,因此可以 内联 map()

1
2
3
4
5
6
7
8
9
10
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()} value={number} />
)}
</ul>
);
}

如果 map() 体中有太多嵌套,是提取组件的好时机。

表单(Forms)

受控组件(Controlled Components)

在 HTML 中,表单元素如 <input>,<textarea> 和 <select> 表单元素通常保持自己的状态,并根据用户输入进行更新

而在 React 中,可变状态一般保存在组件的 state(状态) 属性中,并且只能通过 setState() 更新

“受控组件”:通过使 React 的 state 成为 “单一数据源原则” 来结合这两个形式。然后渲染表单的 React 组件也可以控制在用户输入之后的行为。这种形式,其值由 React 控制的输入表单元素称为“受控组件”。

受控组件示例

设置表单元素的 value 属性之后,其显示值将由 this.state.value 决定,以满足 React 状态的同一数据理念。每次键盘敲击之后会执行 handleChange 方法以更新 React 状态,显示值也将随着用户的输入改变。

event.target 获取触发事件的元素

event.preventDefault() 阻止默认事件

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
class NameForm extends React.Component {
constructor(props) {
super(props);
//初始化state
this.state = {value: ''};

//给事件绑定this
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

//定义输入内容改变事件
handleChange(event) {
this.setState({value: event.target.value});
}
//定义 form提交事件 并阻止默认submit事件
handleSubmit(event) {
alert('A name was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
//form表单 绑定onSubmit事件
<form onSubmit={this.handleSubmit}>
<label>
Name:
//将value与this.state.value进行绑定
//监听onChange事件
//
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

对于受控组件来说,每一次 state(状态) 变化都会伴有相关联的处理函数。这使得可以直接修改或验证用户的输入

比如,如果我们希望强制 name 的输入都是大写字母,可以这样来写 handleChange 方法:

1
2
3
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}

页面显示数据步骤:

state.value -> 用户输入->处理函数  ->state.value

textare 标签

在 HTML 中,<textarea> 元素通过它的子节点定义了它的文本值:

在 React 中,<textarea> 的赋值使用 value 属性替代。这样一来,表单中 <textarea> 的书写方式与单行文本输入框一样

注意,this.state.value 在构造函数中初始化,所以这些文本一开始就出现在文本域中。

1
2
3
4
5
//HTML中
<textarea>
Hello there, this is some text in a text area
</textarea>

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
//React中
class EssayForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 'Please write an essay about your favorite DOM element.'
};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}

handleSubmit(event) {
alert('An essay was submitted: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Essay:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

select 标签: selected

在 HTML 中,<select> 创建了一个下拉列表,通过给 option 设置 selected 属性设置默认选中;

React 中,并不使用这个 selected 属性,而是在根 select 标签中使用了一个 value 属性

可以将一个数组传递给 value 属性 并开启multiple={true} 属性,允许你在 select 标签中选择多个选项

1
2
3
4
5
6
7
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<!--默认选中-->
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
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 FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};

this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}

handleChange(event) {
this.setState({value: event.target.value});
}

handleSubmit(event) {
alert('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}

render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite flavor:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
</label>
<input type="submit" value="Submit" />
</form>
);
}
}

//多选
<select multiple={true} value={['B', 'C']}>

处理多个输入元素

处理多个受控的 input 元素时,可以为每个元素添加一个 name 属性,并且让处理函数根据 event.target.name 的值来决定要做什么

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};

this.handleInputChange = this.handleInputChange.bind(this);
}

handleInputChange(event) {
const target = event.target;
//根据 表单元素的type 拿到数据
const value = target.type === 'checkbox' ? target.checked : target.value;
//拿到 元素的name
const name = target.name;

//使用ES6 计算的属性名称语法来更新与给定输入名称相对应的 state(状态) 键
this.setState({
[name]: value
});
/**
//这段代码等价于 ES5 代码:
var json = {};
json[name] = value;
this.setState(json);
**/
}

render() {
return (
<form>
<label>
Is going:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
Number of guests:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}

受控 Input 组件的 null 值

在 受控组件 上指定值 prop 可防止用户更改输入,除非您希望如此。 如果你已经指定了一个 value ,但是输入仍然是可编辑的,你可能会意外地把 value 设置为undefinednull
以下代码演示了这一点。 (输入首先被锁定,但在短暂的延迟后可以编辑。)

1
2
3
4
5
6
ReactDOM.render(<input value="hi" />, mountNode);

setTimeout(function() {
ReactDOM.render(<input value={null} />, mountNode);
}, 1000);

form插件

Formik 是完整的解决方案,包括验证、跟踪访问的字段以及处理表单提交

不受控组件

推荐使用受控组件来实现表单

在受控组件中,表单数据由 React 组件负责处理。

不受控组件,其表单数据由 DOM 元素本身处理。

不受控组件:使用一个 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);
event.preventDefault();
}

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

不受控组设置默认值:使用defaultValue不是 value

希望 不受控组 有初始值,并且后续更新不受控制,应使用defaultValue 属性而不是 value 属性

<input type="checkbox"> 和 <input type="radio"> 支持 defaultChecked而 <select> 和 <textarea> 支持 defaultValue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input
defaultValue="Bob"
type="text"
ref={(input) => this.input = input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}

file input 标签

在HTML中, <input type="file"> 可以让用户从设备存储器中选择一个或多个文件上传到服务器

因为它的值是只读的,所以它是 React中的 不受控 组件。

1
<input type="file" />
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')
);

状态提升(Lifting State Up)

同一个数据的变化需要几个不同的组件来反映时,需要提升这个共享的状态到用到它的组件的最近的祖先组件中,(依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态)

在一个 React 应用中,对于任何可变的数据都应该遵循“单一数据源”原则

当一个数据可以从 props(属性) 或者 state(状态) 得到,那么不应该从 state(状态) 中获取,而是从props获取(能从props中获取的数据,就不要用state

状态提升,最主要的在于子组件接收父组件的数据、子组件怎么调用父组件的函数来改变父组件的数据,明白这一点以后,就是组件的嵌套

温度计算器示例

temperatureTemperatureInput组件的 本地 state(状态)时, TemperatureInput 可以调用 this.setState() 来更改它。 然而,当 temperature 是父级的state作为 prop(属性)传递进来的 ,TemperatureInput 就无法控制它·

要更新其温度时,它就会调用this.props.onTemperatureChange

代码:

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
const scaleNames = {
c: '摄氏温度',
f: '华氏温度'
};

//转换函数 在摄氏温度和华氏温度之间转换的函数
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}

//控制器 ,收一个字符串 temperature 和一个 转化器函数 作为参数
//例如, tryConvert('abc', toCelsius) 将返回一个空字符串,而 tryConvert('10.22', toFahrenheit) 返回 '50.396' 。

function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}

//BoilingVerdict 的组件,接受 celsius(摄氏温度)作为 prop ,并打印是否足以使水沸腾
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>水沸腾了!</p>;
}
return <p>水没有沸腾!</p>;
}


//可复用输入组件
//TemperatureInput 组件,提取Calculator组件中的输入框,本组件根据传入数据显示华氏温度输入和摄氏输入,并且两者保持自动同步。
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}

handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}

render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}

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
40
41
42
43
44
45
46
47
//父级组件
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
//temperature:调用时传递的参数
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature:temperature});
}

handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature:temperature});
}

render() {
//判断是谁在输入
const scale = this.state.scale;
//读取输入的值
const temperature = this.state.temperature;
//计算出华氏和摄氏温度
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

// 两个组件显示不同的数据
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />

<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />

<BoilingVerdict
celsius={parseFloat(celsius)} />

</div>
);
}
}

数据流程

输入内容改变 >> 触发change调用父级change将输入内容传入父级 >> 父级数据改变 >> 传入子组件 >> 子组件显示父组件传过来的数据

子组件调用父组件的函数

将父级的函数通过props传递给子级,子级通过this.props.xxx就可以调用父级的函数并对父级的数据进行更改

组合(Composition) VS 继承(Inheritance)

建议使用组合不是继承来实现代码的重用。

包含(children prop)

如果组件在设计前无法获知自己要使用什么子组件,那么使用特别的 children prop 来直接传递 子元素到他们的输出中(组件内使用{props.children}接收组件使用时在标签中传入的子级元素)

组件JSX 标签中的任何内容(包括直接写的文字)都会被传递到组件中,作为一个 children prop(属性)通过{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
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
/**
props.children代表:
FancyBorder组件被WelcomeDialog组件调用用时在标签内嵌套的

<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
**/
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}

通过“占位符”,接收多组子元素

在一个组件中可能需要多个 “占位符” ,这种情况下使用自定义的 prop(属性),而不是使用 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
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 />
}
right={
<Chat />
}
/>
);
}

leftright既可以是React元素也可以是HTML标签元素

特例(通过组合实现组件的可配置,props和props.children 组合)

使用组合来实现,一个偏“特殊”的组件渲染出一个偏“通用”的组件,通过 props(属性) 配置它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//函数式组件
function Dialog(props) {
return (
<div color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</div>
);
}

function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!" />

);
}
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
40
41
42
43
//类组件
function Dialog(props) {
return (
<div color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</div>
);
}

class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
handleChange(e) {
this.setState({login: e.target.value});
}

handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />

<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
}

通过组合和继承实现对话框的一种方式

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
40
41
42
43
44
45
46
47
48
49
50
51
52
function Dialog(props) {
if(!props.show){
return null;
}
return (
<div color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</div>
);
}
class SignUpDialog extends React.Component{
constructor(props){
super(props);
this.state={show:false}
this.change=this.change.bind(this)
}
change(e){
console.log(e.target.name);
const show=eval(e.target.name);
this.setState({show:show})
}
render(){
return (
<div>
<Dialog title="添加弹层" message="add" show={this.state.show}>
<button name="false" onClick={this.change}>关闭</button>
</Dialog>
<button name="true" onClick={this.change}>添加</button>
</div>
)
}
}
//或者
//change事件使用在constructor中.bind(this)的方式绑定this
/*
<Dialog title="添加弹层" message="add" show={this.state.show}>
<button name="false" onClick={this.change.bind(this, false)}>关闭</button>
</Dialog>
<button name="true" onClick={this.change.bind(this, true)}>添加</button>
*/

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

使用 props(属性) 和 组合已经足够灵活来明确、安全的定制一个组件的外观和行为,不推荐用继承层次结构来创建组件。切记,组件可以接受任意的 props(属性) ,包括原始值、React 元素,或者函数。

React 的编程思想

步骤1:将 UI 拆解到组件层次结构中
步骤2: 用 React 构建一个静态版本
步骤3: 确定 UI state(状态) 的最小但能完整展现功能的父级组件
步骤4:确定哪个组件可变,或者说哪个组件使用这些 state(状态) 。
步骤5:添加反向数据流(就是:子组件改变父组件数据,一般通过props将改变父组件数据的父组件函数传递给子组件)

可访问性

JSX 中,label 标签的 for 特性被写作htmlFor:

1
2
<label htmlFor="namedInput">Name:</label>
<input id="namedInput" type="text" name="name"/>

片段(fragments)(类似 小程序 的 block标签、vue的 template)

片段(fragments) 可以将子元素列表添加到一个分组中,并且不会在DOM 中增加额外节点

有时候向 JSX 中添加 <div> 元素,会破坏 React 代码正常工作,特别是在使用列表(<ol>,<ul>和<dl>)以及 HTML <table>。在这些情况下,应该使用 React 片段(Fragments) 将多个元素组合在一起。

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 Columns extends React.Component {
render() {
return (
<div>
<td>Hello</td>
<td>World</td>
</div>
);
}
}

class Table extends React.Component {
render() {
return (
<table>
<tr>
<Columns />
</tr>
</table>
);
}
}
//渲染结果 div被渲染了

<table>
<tr>
<div>
<td>Hello</td>
<td>World</td>
</div>
</tr>
</table>
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
//上边的Columns组件  改为使用片段
class Columns extends React.Component {
render() {
return (
<React.Fragment>
<td>Hello</td>
<td>World</td>
</React.Fragment>
);
}
}
//或者
import React, { Fragment } from 'react';
class Columns extends React.Component {
render() {
return (
<Fragment>
<td>Hello</td>
<td>World</td>
</Fragment>
);
}
}


//渲染结果 <React.Fragment>标签不显示
<table>
<tr>
<td>Hello</td>
<td>World</td>
</tr>
</table>

简写语法<></>

<></>,不支持 键(keys) 或 属性(attributes)

1
2
3
4
5
6
7
8
9
10
class Columns extends React.Component {
render() {
return (
<>
<td>Hello</td>
<td>World</td>
</>
);
}
}

绑定kay及属性必须用<Fragment></Fragment>

1
2
3
4
5
6
7
8
9
10
11
12
13
function Glossary(props) {
return (
<dl>
{props.items.map(item => (
// Fragments should also have a `key` prop when mapping collections
<Fragment key={item.id}>
<dt>{item.term}</dt>
<dd>{item.description}</dd>
</Fragment>
))}
</dl>
);
}

插槽(Portal)

将子节点渲染到父组件 DOM 层次结构之外的 DOM 节点(将子节点渲染到 React tree 的任何地方,不限于父节点)

1
ReactDOM.createPortal(child, container)

第一个参数(child)是任何可渲染的 React 子元素(一个元素,字符串或 片段(fragment))

第二个参数(container)则是一个 DOM 元素

通常来说,当从组件的 render 方法返回一个元素时,它将被作为子元素被装载到最近父节点 DOM 中:

1
2
3
4
5
6
7
8
render() {
// React 装载一个新的 div,并将 children 渲染到这个 div 中
return (
<div>
{this.props.children}
</div>
);
}

然而,有时候希望将子元素插入到 DOM 节点的其他位置:

React 不会 创建一个新的 div

children 渲染到 domNode

domNode 可以是任何有效的 DOM 节点,不管它在 DOM 中的位置

1
2
3
4
5
6
7
8
render() {
// React *不* 会创建一个新的 div。 它把 children 渲染到 `domNode` 中。
// `domNode` 可以是任何有效的 DOM 节点,不管它在 DOM 中的位置。
return ReactDOM.createPortal(
this.props.children,
domNode,
);
}

典型用例:

当父组件有 overflow: hiddenz-index 样式,但你需要子组件能够在视觉上 “跳出(break out)” 其容器(子组件不被父组件的overflow: hidden和z-index 影响)。例如,对话框、hovercards以及提示框:

1
2
<div id="app-root"></div>
<div id="modal-root"></div>
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
const appRoot=document.getElementById('app-root');
const modalRoot=document.getElementById('modal-root');

//让我们创建一个模态组件,它是围绕portal API的抽象。
class Modal extends React.Component{
constructor(props){
super(props);
//创建了一个div
this.el=document.createElement('div')
}
componentDidMount(){
//装载div
modalRoot.appendChild(this.el)
}
componentWillUnmount(){
//卸载div
modalRoot.removeChild(this.el)
}
render(){
//利用 createPortal将嵌套的children props 渲染到装载的div中
return ReactDOM.createPortal(
this.props.children,
this.el
)
}
}
//Modal组件是一个普通的React组件,因此我们可以在任何地方呈现它,而无需知道它是通过 portals实现的
class App extends React.Component{
constructor(props){
super(props)
this.state={
showModal:false
}
this.handleShow = this.handleShow.bind(this);
this.handleHide = this.handleHide.bind(this);
}
handleShow() {
this.setState({showModal: true});
}

handleHide() {
this.setState({showModal: false});
}
render(){
//当this.state.showModal 为true时 渲染Modal组件
const modal=this.state.showModal?(
<Modal>
<div className="modal">
<div>
通过portal, 我们可以将内容呈现为 是DOM的一部分,就好像它是任何其他react子对象一样.
</div>
这是在#modal-container div中呈现的
<button onClick={this.handleHide}>Hide modal</button>
</div>
</Modal>
):null;
return (
<div className="app">
这个div 是 overflow: hidden.
<button onClick={this.handleShow}>Show modal</button>
{modal}
</div>
)
}
}

ReactDOM.render(
<App></App>,
appRoot
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#modal-root {
position: relative;
z-index: 999;
}

.app {
height: 10em;
width: 10em;
background: lightblue;
overflow: hidden;
}

.modal {
background-color: rgba(0,0,0,0.5);
position: fixed;
height: 100%;
width: 100%;
top: 0;
left: 0;
display: flex;
align-items: center;
justify-content: center;
}

Portals 存在事件冒泡

虽然portal 可以被放置在 DOM 树的任何地方,但在其他方面其行为和普通的 React 子节点行为一致

如上下文特性(Context)依然能够作用在portal生成的子节点上

由于 portal 仍存在于 React tree 中,所以portal依然存在事件冒泡, portal内部会触发的事件会一直冒泡至包含 React tree 的祖先

严格模式(Strict Mode)为其后代元素触发额外的检查和警告

StrictMode 是一个用以标记出应用中潜在问题的工具。就像 Fragment ,StrictMode 不会渲染任何真实的UI。它为其后代元素触发额外的检查和警告

可以在应用的任何地方启用严格模式

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

function ExampleApplication() {
return (
<div>
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />
</div>
);
}

使用 PropTypes 进行类型检查

使用 prop-types 库 来定义contextTypes

propTypes 仅在开发模式中检测

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
};

验证器列表

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import PropTypes from 'prop-types';

MyComponent.propTypes = {
// 你可以声明一个 prop 是一个特定的 JS 原始类型。
// 默认情况下,这些都是可选的。
optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

// 任何东西都可以被渲染:numbers, strings, elements,或者是包含这些类型的数组(或者是片段)。
optionalNode: PropTypes.node,

// 一个 React 元素。
optionalElement: PropTypes.element,

// 你也可以声明一个 prop 是类的一个实例。
// 使用 JS 的 instanceof 运算符。
optionalMessage: PropTypes.instanceOf(Message),

// 你可以声明 prop 是特定的值,类似于枚举
optionalEnum: PropTypes.oneOf(['News', 'Photos']),

// 一个对象可以是多种类型其中之一
optionalUnion: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),

// 一个某种类型的数组
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

// 属性值为某种类型的对象
optionalObjectOf: PropTypes.objectOf(PropTypes.number),

// 一个特定形式的对象
optionalObjectWithShape: PropTypes.shape({
color: PropTypes.string,
fontSize: PropTypes.number
}),

// 你可以使用 `isRequired' 链接上述任何一个,以确保在没有提供 prop 的情况下显示警告。
requiredFunc: PropTypes.func.isRequired,

// 任何数据类型的值
requiredAny: PropTypes.any.isRequired,

// 你也可以声明自定义的验证器。如果验证失败返回 Error 对象。不要使用 `console.warn` 或者 throw ,
// 因为这不会在 `oneOfType` 类型的验证器中起作用。
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(
'Invalid prop `' + propName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
},

// 也可以声明`arrayOf`和`objectOf`类型的验证器,如果验证失败需要返回Error对象。
// 会在数组或者对象的每一个元素上调用验证器。验证器的前两个参数分别是数组或者对象本身,
// 以及当前元素的键值。
customArrayProp: PropTypes.arrayOf(function(propValue, key, componentName, location, propFullName) {
if (!/matchme/.test(propValue[key])) {
return new Error(
'Invalid prop `' + propFullName + '` supplied to' +
' `' + componentName + '`. Validation failed.'
);
}
})
};

要求单独的 Child

使用 PropTypes.element ,可以指定仅将单一子元素传递给组件,作为子节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import 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

defaultProps 属性为 props 定义默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}

// 指定 props 的默认值:
Greeting.defaultProps = {
name: 'Stranger'
};

// 渲染为 "Hello, Stranger":
ReactDOM.render(
<Greeting />,
document.getElementById('example')
);

声明 defaultProps 作为静态属性

如果父组件没有为 this.props.name 传值,defaultProps 会给其一个默认值。

propTypes 的类型检测是在defaultProps 解析之后发生的,因此也会对默认属性 defaultProps 进行类型检测

1
2
3
4
5
6
7
8
9
10
11
class Greeting extends React.Component {
static defaultProps = {
name: 'stranger'
}

render() {
return (
<div>Hello, {this.props.name}</div>
)
}
}

dangerouslySetInnerHTML 及 style

dangerouslySetInnerHTML:代替在浏览器 DOM 中使用 innerHTML

React 用于代替在浏览器 DOM 中使用 innerHTML

从代码设置HTML是有风险的,因为很容易被 跨站点脚本(XSS) 攻击

React 设置 HTML ,必须使用dangerouslySetInnerHTML 并通过 __html 键传递一个对象

1
2
3
4
5
6
7
function createMarkup() {
return {__html: 'First &middot; Second'};
}

function MyComponent() {
return <div dangerouslySetInnerHTML={createMarkup()} />;
}

style

一般不建议在元素上应用 style 属性

渲染时使用 style 来添加动态计算的样式

JavaScript DOMstyle 属性一致,但是更高效,并且防止XSS安全漏洞

设置和获取都是用 驼峰命名

style 接受一个对象,这个对象里面是这个元素的 CSS 属性键值对,原来 CSS 属性中带 - 的元素都必须要去掉 - 换成驼峰命名,如 font-size 换成 fontSizetext-align 换成 textAlign

1
2
3
4
5
6
7
8
const divStyle = {
color: 'blue',
backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {
return <div style={divStyle}>Hello World!</div>;
}

样式不自动进行兼容。 要支持旧版本的浏览器,需要提供相应的样式属性

1
2
3
4
5
6
7
8
const divStyle = {
WebkitTransition: 'all', // 注意这里的大写首字母 'W'
msTransition: 'all' // 'ms' 'ms'是唯一的小写字母的浏览器前缀
};

function ComponentWithTransition() {
return <div style={divStyle}>This should work cross-browser</div>;
}

React 会自动为某些内联样式的数字属性值附加一个 “px” 后缀,不想使用px时应该明确单位

1
2
3
4
5
6
7
8
9
// 结果样式: '10px'
<div style={{ height: 10 }}>
Hello World!
</div>

// 结果样式: '10%'
<div style={{ height: '10%' }}>
Hello World!
</div>