本文是对React文档:核心概念部分的笔记,内容大致与文档相同。
文档链接
React哲学部分写的很好,务必要看
JSX
JSX是JS的语法扩展,配合react使用,为JS和HTML的混写
JSX支持在大括号({})中书写任何有效JS表达式
同时,JSX也是一个表达式
如下面的例子:
const name="josh perez"
const element=<h1>Hello,{name}</h1>
ReactDOM.render(
element,
document.getElementById("root")
);
在属性中嵌入表达式有两种方法:
//使用引号包裹的字面值
const element =<div tabIndex="0"></div>
//使用花括号包裹的表达式
const element=<img src={user.name}></img>
JSX的属性名称使用小驼峰命名,如class->className
React在渲染已经输入的内容前,会默认进行转义,所有的内容在渲染前已经被转换为字符串,可以有效防止XSS
JSX对象:
Babel将会把JSX转译为一个名为React.createElement()的函数调用
即:
const element=(
<h1 className="greet">
hello
</h1>
);
//与下面的写法相同
const element=React.createElement(
"h1",
{className:"greet"},
"hello"
);
//它事实上创建了一个简化的如下的对象:
const element={
type:"h1",
props:{
className:"greet",
children:"hello"
}
};
它们也被称为React元素,描述了希望在屏幕上看到的内容,React通过读取这些对象来使用他们构建DOM
元素渲染
元素是构成React应用的最小块
React元素是创建开销很小的普通对象。React DOM更新DOM来与React元素保持一致
React 根 DOM 节点
React 根 DOM 节点大概是类似这样的结构:
这个节点内的所有内容都由React DOM管理。 对于根DOM节点,使用ReactDOM.render()进行渲染const element=<h1>hello</h1>
ReactDOM.render(element,document.getElementById("root"));//root就是根DOM节点的id值
React元素是不可改变对象,一旦被创建就不能修改。更新UI的唯一方法就是创建一个全新的元素。
React 更新策略
React DOM只会进行必要的更新来使DOM达到新的状态。
这种策略只考虑UI在一个指定时间的状态,而不考虑变化的过程,可以减少bug。
组件&props
组件即为代码复用的片段,类似于函数。
组件形式
组件有两种形式:函数和class
//函数组件
function Welcome(props){
return <h1>hello,{props.name}</h1>
}
//ES6-class
class Welcome extends React.Component{
render(){
return(
<div>
hello,{this.props.name}
</div>
);
}
}
渲染组件
React元素可以是DOM标签、或者是用户自己定义的组件
当用户自定义组件,它会将JSX接收道德属性以及子组件转换为单个对象传递给组件,即props
看如下例子:
class Welcome extends React.Component{
render(){
return(
<div>
hello,{this.props.name}
</div>
);
}
}
const elemen=<Welcome name="柳刀" />
ReactDOM.render(
element,
document.getElementById('root')
);
页面会渲染出:hello,柳刀
注意:自定义标签的名称必须为大写,React将会把小写字母开头的组件视为原生DOM组件。
组件的组合与提取
组件可以进行嵌套组合
通常每个新的React应用程序的顶层组件都应该是App组件
对于一些使用程度叫较高的组件,应该进行额外的定义以使组件更容易维护。
props只读
所有的React组件都必须保护它们的props不被更改
state&生命周期
state与props相似,但是可以完全受控与当前的组件。
组件第一次被渲染到DOM中时,被称为“挂载”(mount)
DOM中组件被删除的时候,应该清除计时器,被称为“卸载”(unmount)
见如下的时钟的例子:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//生命周期方法
//在组件已经被渲染到DOM中后运行
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//在组件即将被卸载时调用
componentWillUnmount() {
clearInterval(this.timerID);
}
//计时器函数,通过这个函数更新state
tick() {
this.setState({
date: new Date()
});
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
state
state只能在组件初始化时进行赋默认值
state={}
或者在构造函数中进行构造;
state不能在除了以上两个地方之外使用 this.state.xxx 进行更新
只能使用
this.setState({xxx:xxx}) 进行更新
state的更新可能是异步的
React可能会将多个setState()合并为一个调用,而且props,state也可能异步更新,所以不应该依赖它们的值去决定下一个状态。
如:
//wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
为了解决该问题,可以使用:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
使用参数对state进行更新
state的更新会被合并
为了提高性能,React将setState()合并调用,这里的合并是浅合并,简而言之,就和单独调用他们的效果是一样的。
数据是向下流动的
这被称为"自上而下"或者是"单向"数据流,任何的state总是所属于特定的组件,而从state派生的任何数据只能影响树中低于它们的组件。
这类似与一个瀑布,上层组件在瀑布上层,下层组件在瀑布下层,props是水流,state就是各个组件的水源。
React的数据流动是单向的,也是单层的,即数据不能跨组件流动,但是数据流能够跨组件流动。
事件处理
React事件的命名采用小驼峰而不是纯小写
使用JSX时需要传入一个函数作为事件处理函数而不是一个字符串
//传统
<button onclick="activateLasers()">
Activate Lasers
</button>
//React
<button onClick={activateLasers}>
Activate Lasers
</button>
React中不能通过返回false阻止默认的行为,必须显示地使用preventDefault(取消默认行为)
//传统html
<a href="#" onclick="console.log('The link was clicked.'); return false">
Click me
</a>
//React
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
使用class定义组件,事件处理函数通常为一个方法
处理this
class不会默认绑定this,需要手动进行绑定
可以进行:
• 构造函数中绑定this
this.handle=this.handle.bind(this)
• 在调用时绑定this
onClick={this.handle.bind(this)}
• 在声明时使用箭头函数:class fields语法
handle=()=>{}
• 在回调中使用箭头函数
onClick={() => this.handleClick()}
这个方法的问题是在每次渲染组件的时候都会创建不同的回调函数,作为props传入子组件时,这些子组件可能会进行额外的重新渲染,所以不推荐使用。
向事件处理程序传递参数
有两种方法:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
条件渲染
元素变量
即通过某个state中的状态进行渲染元素的判断,使用这种方法可以增加代码重用。
&&
例子如下:
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
以上例子能够实现是因为:
在JS中,true&&experssion总是返回experssion
false&&experssion总是返回false
三目运算
这里要注意的是,要在可读性和简便性上做好平衡,保持代码的可读性。
如果代码太复杂,应该提取组件。
阻止组件渲染
不希望渲染某组件时,可以直接render null
以下的例子中将不会进行渲染,用这种方法可以在必要的时候阻止特定组件的渲染。
class ClassName extends React.Component{
render(){
return null;
}
}
组件render返回null并不会影响组件的生命周期。
列表&Key
渲染多个组件
在React中渲染多个组件一般使用map方法,从已有数组中产生新数组。
下面的例子进行了解释:
const numbers=[1,2,3,4,5]
const listItems=numbers.map((number)=><li>{number}</li>);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
基础列表组件
规范化,在一个组件中渲染列表:
class NumberList(props){
const numbers=props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);
return (
<ul>{listItems}</ul>
)
}
这个例子的作用与前面的例子相同,只不过为每个
key
key的作用是帮助React识别哪些元素发生了改变,所以需要给数组中的每个元素赋予一个特定的值。
key应该是元素在列表中拥有的独一无二的字符串,通常使用数据中的id作为元素的key。
如果传入的数据没用id,可以使用元素索引index作为key
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
但是如果列表项目的顺序可能发生变化,那不建议使用索引作为key值,这样会使性能变差,还会引起组件状态问题,如果不显示指定key值,React将默认使用索引作为key值。
用key提取组件
元素的key应该放在就近数组的上下文中。
如例子:
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.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>
);
}
在上面的例子中,应该在下文,即ListItem处引入数组key,因为它才是数组中的“直接”元素。
在大多数情况下,在map()方法中的元素设置key属性总是正确的。
key只是在兄弟节点之间必须唯一
数组元素只需要在它所在的元素中唯一即可,不需要全局唯一,在生成两个数组的时候,可以使用相同的key值。
key只会传递消息给React,而不会传递给所在的组件。需要在组件中使用key值时,应该使用其他的属性名称显式传递值。
在JSX中嵌入map()
可以将遍历返回的结果通过map()写在{}中,因为JSX允许在大括号嵌入任何表达式。
如果一个 map() 嵌套了太多层级,那可能就是你提取组件的一个好时机
表单
受控组件
受控组件即为使React成为state的唯一数据源。渲染表单的React组件还控制着用户输入过程中表单发生的操作。
如下例:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
在这个例子中,名字表单的值始终为this.state.value,React为state唯一的数据源,handleChange将会在每次案件时执行并更新state。
对于受控组件,每个state突变都有一个相关的处理函数,这使得修改或者验证用户输入变得更容易。例如可以强制将用户输入大写字母。
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea标签
在html中,textarea通过子元素定义文本。
在react中,使用value属性代替,使得使用<textarea>
的表单和使用单行input表单非常类似。
select标签
在react中,select使用value属性
见例子:
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('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交" />
</form>
);
}
}
以上三个组件都接收一个value属性,可以使用这个属性来实现受控组件。
可以将数组传入到value属性中,以支持select标签中的多个选项。
文件input
在html中,
<input type="file" />
的value为只读,所以是一个非受控组件
处理多个输入
处理多个input元素时,可以给每个元素添加name属性,并让函数根据event.target.name的值选择要执行的操作。
看下面例子:
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;
const value = target.name === 'isGoing' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
参与:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
来宾人数:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
值得注意的是,使用以下语句进行state的更新
this.setState({
[name]: value
});
这是ES6计算属性名称的语法。
受控输入空值
在受控组件上指定value的prop会阻止用户更改输入。如果指定value,输入仍可编辑,可能是意外将value设置为undefined&null
非受控组件
状态提升
多个组件需要反映相同的变化数据,这时建议将共享状态提升到最近的共同父组件中去。
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'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
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();
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
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>
);
}
}
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>
);
}
}
关于状态提升的总结
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
虽然提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但带来的好处是,排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。此外,你也可以使用自定义逻辑来拒绝或转换用户的输入。
如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。举个例子,本例中我们没有将 celsiusValue 和 fahrenheitValue 一起保存,而是仅保存了最后修改的 temperature 和它的 scale。这是因为另一个输入框的温度值始终可以通过这两个值以及组件的 render() 方法获得。这使得我们能够清除输入框内容,亦或是,在不损失用户操作的输入框内数值精度的前提下对另一个输入框内的转换数值做四舍五入的操作。
组合VS继承
React推荐使用组合而不是继承来实现组件间的代码重用
包含关系
有些组件在使用前无法得知其子组件的内容,这样的情况可以见例子如下:
class Part1 extends React.Componment{
render(){
return(
<div>
{props.children}
</div>
);
}
}
class Part2 extends React.Componment{
render(){
return(
<div>
子组件内容
</div>
);
}
}
该结构即在父组件中为子组件留出位置,该位置可以用例子中的 {props.children} 表示,也可以使用
props.left 或者其他的表达式表示。
特例关系
存在某个组件是一个组件的特例的情况,该情况也可以使用组合进行标识
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
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>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
在以上例子中,SignUpDialog是Dialog的特例,只要把SignUpDialog作为Dialog的父组件,将数据流向Dialog即可。
继承
FaceBook那么多页面都没用到继承,React的组合这么好用了,别用什么继承了。
注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
如果需要在组件间复用非UI功能,建议提取为单独的模块,使用import引入。