学习笔记
1.条件渲染
必须谨慎对待JSX回调函数中的this,类的默认方法是不会绑定this的。如果你忘记绑定this.handleClick并把它传入onClick,当你调用这个函数的时候this值会是undefined。
这并不是React的特殊行为;它是函数如何在js中运行的一部分。通常情况下,如果你没有在方法后添加(),例如onClick={this.handleClick},你应该为这个方法绑定(bind)this。
2.条件渲染
image.png实现目标:点击Login后显示“Welcome back”,按钮变为Logout。
拆分成两个大组件Greeting和Button,Greeting组件根据isLoggedIn参数是否为true分别显示函数组件UserGreeting(Welcome back!)、GuestGreeting(Please sign up)
疑问:
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
)
}
1.为什么要带参数props
2.onClick里面为什么是props.onClick
可以用if语句、JSX表达式、逻辑与&&、三目运算符等进行条件渲染
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')
);
在 JavaScript 中,true && expression 总是返回 expression,而 false && expression 总是返回 false。如果条件是 true,&& 右侧的元素就会被渲染,如果是 false,React 会忽略并跳过它。
image.png如下,组件的 render 方法返回 null 并不会影响该组件生命周期方法的回调。例如,componentWillUpdate 和 componentDidUpdate 依然可以被调用。
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(() => ({
showWarning: ! this.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')
);
3.列表&keys
在js中如何转化列表,如下,将数组中的每一项翻倍
const numbers = [1, 2, 3, 4, 5];
const doubled = numbers.map((number) => number * 2);//注意此处需要number参数,仅仅是一个标识
console.log(doubled);
可以使用{}在JSX内构建一个元素集合
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
这段代码生成了一个1到5的数字列表.
keys
keys可以在DOM中的某些元素被增加或删除时帮助React识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
元素的key最好是这个元素在列表中的一个独一无二的字符串,通常使用数据id,如果没有id可以使用它的序列号索引index作为key
const todoItems = todos.map((todo, index) =>
<li key={index}> //或者<li key={todo.id}>
{todo.text}
</li>
);
如果需要给列表重新排序,不建议用索引排序,因为这会导致渲染变得很慢。
用keys提取组件
元素的key只有在它和它的兄弟节点对比时才有意义。比方说,如果你提取出一个ListItem组件,你应该把key保存在数组中的这个<ListItem />元素上,而不是放在ListItem组件中的<li>元素上。并不是说不能放在li元素,下面的Blog例子就可以
正确使用方法:
function ListItem(props) {
return <li> {props.value}</li>
}
function NumberList(props) {
const numbers = props.numbers;
const ListItems = numbers.map((number) =>
<ListItem key={number.toString()} value={number}/>
);
return (
/* 此处要把ListItem包在ul里,不能在最后的ReactDOM.render中包一层ul,
因为React里不能把对象作为一个孩子节点,如果要render多个孩子,使用数组或用对象把孩子包起来.
这也解释了为什么要定义这两个函数组件,一个生成li一个包装li
*/
<ul>
{ListItems}
</ul>
);
}
const numbers = [1,2,3,4];
ReactDOM.render(
<NumberList numbers={numbers}/>,
document.getElementById('root')
);
当在map()方法内部调用元素时,最好为每一个元素加上独一无二的key。
元素的key在它的兄弟元素之间应该唯一
数组元素中使用的key在其兄弟之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的键。
function Blog(props) {
const sidebar = (
<ul>
{props.post.map((post)=>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.post.map((post)=>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr/>
{content}
</div>
)
}
const post = [
{id:1,title:"Hello World",content:"Welcome to learn React"},
{id:2,title:"Installation",content:"You can install React from npm"}
];
ReactDOM.render(
<Blog post={post} />,
document.getElementById('root')
);
key会作为给React的提示,但是不会传递给你的组件。如果需要使用key,请将其作为属性传递。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
上面例子中,Post组件可以读出props.id但读不出props.key。
在JSX中嵌入map()
在上面例子中,我们声明了一个单独的ListItem变量并将其包含在JSX中,JSX运行在大括号中嵌入任何表达式,可以在map()中这样使用:
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
这么做有时会让代码清晰,有时会造成滥用。何时为了可读性提取出组件完全取决于你,但是如果一个map()嵌套了太多层,可能就是提取组件的一个好机会。
4.表单
HTML表单与React中其他DOM元素不同,因为表单元素生来就保留一些内部状态。例如下面这个表单只接受一个唯一的name
<form>
<label>
Name:
<input type="text" name="name" />
</label>
<input type="submit" value="Submit" />
</form>
当用户提交表单时,HTML的默认行为会使这个表单跳转到一个新页面。React中也是如此,大多数情况下,我们会构造一个处理提交表单并可访问用户输入表单数据的函数。实现这一点的标准方法是使用“受控组价”。
受控组件:
<input>和<select>都要绑定一个change事件,每当表单发生变化时,都会被写入组件的state中,这种组件在React中被称为受控组件。在受控组件中,组件渲染出的状态与它的value或者checked prop相对应。React通过这种方式消除了组件的局部状态,使得应用整个状态可控。React受控组件更新State的流程如下:
1)可以通过初始state中设置表单的默认值
2)每当表单值发生变化时,调用onChange事件处理器
3)事件处理器通过合成实践对象e拿到改变后的状态,并更新state
4)setState触发视图的重新渲染,完成表单组件值的更新
React中的数据是单向流动的,表单的数据来源于组件的state,并通过props传入,这也成为单向数据绑定。然后通过onChange事件处理器将新的表单数据写回到state,完成了双向数据绑定。
我们通过使react变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时所发生的变化。相应的,其值由React控制的输入表单元素称为“受控组件”。
在HTML当中,像<input>
,<textarea>
, 和 <select>
这类表单元素会维持自身状态,并根据用户输入进行更新。但在React中,可变的状态通常保存在组件的状态属性中,并且只能用 setState()
方法进行更新。
我们通过使React变成一种单一数据源的状态来结合二者。React负责渲染表单的组件仍然控制用户后续输入时发生的变化。
例如,我们想要使上个例子中在提交表单时输出name,我们可以写成“受控组件”的形式:
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('A name was submitted: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
使用”受控组件”,每个状态的改变都有一个与之相关的处理函数。这样就可以直接修改或验证用户输入。例如,我们如果想限制输入全部是大写字母,我们可以将handleChange 写为如下:
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea标签
React中,<textarea>会用value属性代替,这样表单中的<textarea>类似于使用单行输入的表单。
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>
Name:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
注意this.state.value是在构造函数中初始化,这样文本区域就能获取到其中的文本。
select标签
在React中并不使用selected属性,而是在根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('Your favorite flavor is: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Pick your favorite La Croix 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>
);
}
}
总之,<input type="text">, <textarea>, 和 <select> 都十分类似 - 他们都通过传入一个value属性来实现对组件的控制。
多个输入的解决方案
当你有处理多个受控的input元素时,你可以通过给每个元素添加一个name属性,来让处理函数根据 event.target.name的值来选择做什么。也就是用一个控制函数管理多个input框内容的改变。
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.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
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>
);
}
}
5.状态提升
几个组件共用状态数据时,最好把这部分共享的状态提升至离它们最近的父组件中管理。
例子:温度计算器判断水是否会在指定温度下烧开
为什么App.js里没有名为App的组件, 在index.js里import App from './App';最后ReactDOM.render(<App />, document.getElementById('root'));也没出错??console.log(App);是函数对象
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>水会烧开</p>;
}
return <p>水不会烧开</p>;
}
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>输入一个摄氏温度</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
export default Calculator;
legend 元素为 [fieldset 元素]定义标题(caption)。
添加第二个输入框
在输入摄氏温度的基础上,再提供一个华氏温度,并且他们能保持同步。
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
)
Tip: const rounded = Math.round(output * 1000) / 1000;取小数点后三位小数
Math.round四舍五入
先看一个其他状态提升的小例子:
image.pngclass Child_1 extends React.Component {
render() {
return (
<div>
<h1>{this.props.txt+"1"}</h1> //引可不加 因为this.props.txt是字符串
</div>
)
}
}
class Child_2 extends React.Component {
// constructor(props) { //构造函数可要可不要
// super(props);
// }
render() {
return (
<div>
<h1>{this.props.txt+2}</h1>
</div>
)
}
}
class Child extends React.Component {
constructor(props) {
super(props) ;
this.state = {txt:'hello'};
this.handleChange = this.handleChange.bind(this);
}
handleChange (e) {
this.setState({txt:e.target.value});
}
render(){
return (
<div>
//input里的value可不要 不影响输出
<input type="text" onChange={this.handleChange} value={this.state.txt}/>
<h1>{this.state.txt}</h1>
<Child_1 txt={this.state.txt}/>
<Child_2 txt={this.state.txt}/>
</div>
)
}
}
export default Child;
isNaN和Number.isNaN: 在ES5中判断一个变量是否为NaN采用isNaN(),但是对于特殊变量,这个方法就出现了不符合预期的结果:
isNaN(NaN) //true
isNaN(undefined) //true
isNaN('qwer') //true
isNaN(123) //false
在ES6中推出了新的方法Number.isNaN(),符合预期:
Number.isNaN(undefined) //false
Number.isNaN(NaN) //true
Number.isNaN('qwer') //false
Number.isNaN(123) //false
全局方法isNaN()会先将参数转为Number 类型,在判断是否为NaN ,所以在类型转换失败或运算错误时值为NaN,返回true,其他全为false;Number.isNaN()先判断参数类型,在参数为Number的前提下运算错误时值为NaN返回true,其他全为false
ps:NaN是一个number类型
isNaN("1"); // fales "1" 被转化为数字 1,因此返回false
isNaN("SegmentFault"); // true "SegmentFault" 被转化成数字 NaN
Number.isNaN('0/0') //string not number false
isNaN('0/0') //arithmethic ilegal (NaN) true
Number.isNaN('123') //string not number false
isNaN('123') //convert to number false
Number.isNaN('Hello') //string not number false
isNaN('Hello') //convert fail(NaN) true
回到温度的例子,两个输入框要联动,要是组件之间双向绑定没试过行不行?
这样单一数据源从上至下的流动是官方推荐的写法。
const scaleNames = {
c:'Celsius',
f:'Fahrenheit'
};
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
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 BoilingVerdict(props) {
if(props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
class TemperatureInput extends React.Component{
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
//所有的this.props.属性都是由父组件提供的
this.props.onTemperatureChange(e.target.value);
}
render() {
//此处temperature不管是c还是f,传给我什么我展示什么。通用组件
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>
);
}
}
class Calculator extends React.Component{
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
//什么叫状态提升至父组件呢?把tmperature在父组件的state里定义,这样每次更新该值就是在父组件里
this.state = {temperature:'',scale:'c'};
}
handleCelsiusChange(temperature) {
//在这里更新输入的temperature
this.setState({scale:'c',temperature})
}
handleFahrenheitChange(temperature) {
this.setState({scale:'f',temperature})
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
//scale属性用来判断在哪个输入框输入数字,再转换出另一个框的数字
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
//在这里将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>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
疑问:<input/>里的value是为了获取还是为了传递?
5.组合 vs 继承
React官方建议使用组合而非继承实现代码的复用
以下介绍几个新手常用继承解决的问题,看看使用组合的解决方案
alert(`Welcome aboard ${this.state.login}`); //不能使用“”,要用``才能识别里面的this.state.login