jsx
const element = <h1>Hello, world!</h1>;
这就是 JSX ,他是 JavaScrip 的一种扩展语法。我们推荐在 React 中使用这种语法来描述 UI 信息。JSX 可能会让你想起某种模板语言,但是它具有 JavaScrip 的全部能力。
JSX 表示对象
- Babel 将JSX编译成 React.createElement() 调用。
- 下面的两个例子是是完全相同的:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
React 只更新必需要更新的部分
React DOM 会将元素及其子元素与之前版本逐一对比, 并只对有必要更新的 DOM 进行更新, 以达到 DOM 所需的状态。
组件(Components) 和 属性(Props)
愚人码头注:Props , 即属性(Property), 在代码中写作 props , 故可用 props 指代 properties .
- 函数式组件
- 类组件
警告:
组件名称总是以大写字母开始。
举例来说, <div /> 代表一个 DOM 标签,而 <Welcome /> 则代表一个组件,并且需要在作用域中有一个 Welcome 组件。
提取组件
- 不要害怕把一个组件分为多个更小的组件。
- 组件化思维
Props 是只读的
-
“纯函数”
因为它们不会试图改变它们的输入,并且对于同样的输入,始终可以得到相同的结果。 - 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
状态(State) 和 生命周期
example:
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);
使用state改进
state 和 props 类似,但是它是私有的,并且由组件本身完全控制。
- 把函数式组件转化为类组件:
- 创建一个继承自
React.Component
类的 ES6 class 同名类。- 添加一个名为
render()
的空方法。- 把原函数中的所有内容移至
render()
中。- 在
render()
方法中使用this.props
替代props
。- 删除保留的空函数声明。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 在类组件中添加本地状态(state)
引进 类构造函数(class constructor) 初始化this.state
:
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>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
- 在类中添加生命周期方法
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>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
-组件调用过程
- 当
<Clock />
被传入ReactDOM.render()
时,React
会调用Clock
组件的构造函数。 因为Clock
要显示的是当前时间,所以它将使用包含当前时间的对象来初始化this.state
。我们稍后会更新此状态。
2.然后 React
调用了 Clock
组件的 render()
方法。 React
从该方法返回内容中得到要显示在屏幕上的内容。然后,React
然后更新DOM
以匹配 Clock
的渲染输出。
3.当Clock
输出被插入到DOM
中时,React 调用 componentDidMount()
生命周期钩子。在该方法中,Clock
组件请求浏览器设置一个定时器来一次调用 tick()
。
4.浏览器会每隔一秒调用一次 tick()
方法。在该方法中, Clock
组件通过 setState()
方法并传递一个包含当前时间的对象来安排一个UI
的更新。通过 setState()
, React
得知了组件 state
(状态)的变化, 随即再次调用render()
方法,获取了当前应该显示的内容。 这次,render()
方法中的this.state.date
的值已经发生了改变, 从而,其输出的内容也随之改变。React
于是据此对DOM
进行更新。
5.如果通过其他操作将 Clock 组件从 DOM
中移除了, React
会调用 componentWillUnmount()
生命周期钩子, 所以计时器也会被停止。
正确地使用 State(状态)
- 不要直接修改 state(状态)
// 错误
this.state.comment = 'Hello';
// 正确
this.setState({comment: 'Hello'});
- state(状态) 更新可能是异步的
React 为了优化性能,有可能会将多个 setState() 调用合并为一次更新。
因为 this.props 和 this.state 可能是异步更新的,你不能依赖他们的值计算下一个state(状态)。
- state(状态) 更新可能是异步的
例如, 以下代码可能导致 counter(计数器)更新失败:
// 错误
this.setState({
counter: this.state.counter + this.props.increment,
});
要弥补这个问题,使用另一种 setState() 的形式,它接受一个函数而不是一个对象。这个函数将接收前一个状态作为第一个参数,应用更新时的 props 作为第二个参数:
// 正确
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
state(状态)更新会被合并
当你调用 setState(), React 将合并你提供的对象到当前的状态中。
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
合并是浅合并,所以 this.setState({comments}) 不会改变 this.state.posts 的值,但会完全替换this.state.comments 的值。
数据向下流动
如果把组件树想像为 props(属性) 的瀑布,所有组件的 state(状态) 就如同一个额外的水源汇入主流,且只能随着主流的方向向下流动。
处理事件
- 取消点击默认行为
React 中你不能通过返回 false(愚人码头注:即 return false; 语句) 来阻止默认行为。必须明确调用 preventDefault
function ActionLink() {
function handleClick(e) {
// e 无兼容性问题
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
this的绑定及事件参数传递
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
述两行代码是等价的,分别使用 arrow functions 和 Function.prototype.bind
。
条件渲染
- 有状态组件
它将渲染<LoginButton />
或者<LogoutButton />
,取决于当前状态。同时渲染<Greeting />
组件:
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 = null;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
return (
<div>
<Greeting isLoggedIn={isLoggedIn} />
{button}
</div>
);
}
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
使用逻辑 && 操作符的内联 if 用法
您可以 在JSX中嵌入任何表达式 ,方法是将其包裹在花括号中。这也包括 JavaScript 逻辑 &&
运算符。
使用条件操作符的内联 If-Else
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn ? (
<LogoutButton onClick={this.handleLogoutClick} />
) : (
<LoginButton onClick={this.handleLoginClick} />
)}
</div>
);
}
防止组件渲染
- 在极少数情况下,您可能希望组件隐藏自身,即使它是由另一个组件渲染的。为此,返回 null 而不是其渲染输出。
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(prevState => ({
showWarning: !prevState.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')
);
列表(Lists) 和 键(Keys)
- 可以创建元素集合,并用一对大括号
{}
在 JSX 中直接将其引用即可。
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
example:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li>{number}</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
-
当运行上述代码的时候,将会收到一个警告:a key should be provided for list items(应该为列表元素提供一个键)(愚人码头注 :CodeOpen 中没有报警告,是因为其示例中使用的是 min 版本的 React,换成非 min 版本的就可以看到)。
-
解决丢失 key 的问题。
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
键(Keys)
- 键(Keys) 帮助 React 标识哪个项被修改、添加或者移除了。数组中的每一个元素都应该有一个唯一不变的键(Keys)来标识,
- 挑选 key 最好的方式是使用一个在它的同辈元素中不重复的标识字符串。多数情况你可以使用数据中的 IDs 作为 keys:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
- 当要渲染的列表项中没有稳定的 IDs 时,你可以使用数据项的索引值作为 key 的最后选择:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
如果列表项可能被重新排序时,我们不建议使用索引作为 keys,因为这导致一定的性能问题,会很慢。
使用 keys 提取组件
- 错误的 key 用法
- 一个好的经验准则是元素中调用 map() 需要 keys 。
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>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
keys 在同辈元素中必须是唯一的
- 在数组中使用的 keys 必须在它们的同辈之间唯一。然而它们并不需要全局唯一。
- 键是React的一个内部映射,但其不会传递给组件的内部。如果你需要在组件中使用相同的值,可以明确使用一个不同名字的 prop 传入。
const content = posts.map((post) =>
<Post
key={post.id}
id={post.id}
title={post.title} />
);
上面的例子中, Post 组件可以读取 props.id,但是不能读取 props.key 。
在 JSX 中嵌入 map()
- 在上面的例子中,我们单独声明了一个 listItems 变量,并在 JSX 中引用了该变量:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
// 或者jsx使用大括号
function NumberList(props) {
const numbers = props.numbers;
return (
<ul>
{numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
)}
</ul>
);
}
表单(Forms)
- select 标签
在 HTML 中,<select> 创建了一个下拉列表。例如,这段 HTML 创建一个下拉的口味(flavors)列表:
<select>
<option value="grapefruit">Grapefruit</option>
<option value="lime">Lime</option>
<option selected value="coconut">Coconut</option>
<option value="mango">Mango</option>
</select>
注意,Coconut 选项是初始化选中的,因为它的 selected 属性。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 属性可以用来实现一个受控组件。
file input 标签
<input type="file" />
在 React 中,一个 <input type =“file”/> 和一个普通的 <input /> 类似,但有一个重要的区别:它是只读的(read-only)。 (您不能以编程方式设置值。)相反,你应该使用 File API 与文件进行交互。
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(
this
);
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${
this.fileInput.files[0].name
}`
);
}
render() {
return (
<form
onSubmit={this.handleSubmit}>
<label>
Upload file:
<input
type="file"
ref={input => {
this.fileInput = input;
}}
/>
</label>
<br />
<button type="submit">
Submit
</button>
</form>
);
}
}
ReactDOM.render(
<FileInput />,
document.getElementById('root')
);
处理多个输入元素
- 当您需要处理多个受控的 input 元素时,您可以为每个元素添加一个 name 属性,并且让处理函数根据 event.target.name 的值来选择要做什么。
forExample:
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>
);
}
}
状态提升(Lifting State Up)
- 两个组件都将其值保持在本地状态中,但是我们希望弄些状态是共用的,在React中,共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。
- 我们知道 props(属性) 是只读的。在 React 中,通常通过使组件“受控”的方式来解决。就像 DOM <input>一样接受一个 value 和一个 onChange prop(属性)
forExample:
handleChange(e) {
// 之前是: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
onTemperatureChange prop(属性) 和 temperature prop(属性) 一起由父级的 Calculator 组件提供。它将通过修改自己的本地 state(状态) 来处理变更,从而通过新值重新渲染两个输入。我们将很快看到新的 Calculator 实现。
经验总结
- 在一个 React 应用中,对于任何可变的数据都应该循序“单一数据源”原则。通常情况下,state 首先被添加到需要它进行渲染的组件。然后,如果其它的组件也需要它,你可以提升状态到它们最近的祖先组件。你应该依赖 从上到下的数据流向 ,而不是试图在不同的组件中同步状态。
组合 VS 继承
包含
- 我们建议这种组件使用特别的 children prop 来直接传递 子元素到他们的输出中(好比vue的slot):
forExample:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
这允许其他组件通过嵌套 JSX 传递任意子组件给他们:
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(属性),而不是使用 children :
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 />
} />
);
}
React 的编程思想
-
该如何拆分组件呢?
其实只需要像拆分一个新方法或新对象一样的方式即可。一个常用的技巧是单一职责原则,即一个组件理想情况下只处理一件事。如果一个组件持续膨胀,就应该将其拆分为多个更小的组件中。 -
不要重复你自己 (DRY,don’t repeat yourself)
-
让我们仔细分析每一个数据,弄清楚哪一个是 state(状态) 。请简单地提出有关每个数据的 3 个问题:
- 是否通过 props(属性) 从父级传入? 如果是这样,它可能不是 state(状态) 。
- 是否永远不会发生变化? 如果是这样,它可能不是 state(状态)。
- 是否可以由组件中其他的 state(状态) 或 props(属性) 计算得出?如果是这样,则它不是 state(状态)。
React 单向数据流在层级中自上而下进行。这样有可能不能立即判断出状态属于哪个组件。这常常是新手最难理解的一部分,试着按下面的步骤分析操作:
- 确定每个基于这个 state(状态) 渲染的组件。
- 找出公共父级组件(一个单独的组件,在组件层级中位于所有需要这个 state(状态) 的组件的上面。愚人码头注:父级组件)。
- 公共父级组件 或者 另一个更高级组件拥有这个 state(状态) 。
- 如果找不出一个拥有该 state(状态) 的合适组件,可以创建一个简单的新组件来保留这个 state(状态) ,并将其添加到公共父级组件的上层即可。
网友评论