React Note - Basic
React 学习笔记(基础篇)。
安装 🔗
npm install -g create-react-app
create-react-app hello-world
cd hello-world
npm start
实践:create 这一步会同时执行 npm install
因此有失败的可能,多尝试几次就成功了。
这个程序跟 vue-loader 很像,会造出一个简单的手脚架,包含了 Babel 编译器以及打包工具等等。但是细看它的 package.json
文件并没有包含上述内容:
"devDependencies": {
"react-scripts": "0.8.5"
},
"dependencies": {
"react": "^15.4.2",
"react-dom": "^15.4.2"
}
因此,跟 vue-loader 不一样的是,react 这个手脚架把无关内容都封装了。这么做我觉得有利有弊:它让人用起来更方便,然而不可能达到直接使用原组件的自由度了。相比之下,这里我更喜欢 vue-loader 的处理方式。
Hello World 🔗
最简示例:
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('root')
);
JSX 语法 🔗
JSX 是 JavaScript 的一种语法扩展,实际上可以看做是语法糖。通过编译器,以下语法:
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
相当于:
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world'
}
};
后者就是编译后的结果,JSX 语法块变成了一个对象(称之为 React element
)。
(JB 家的 IDE 已经对 JSX 语法提供了默认支持,不然这篇笔记就到此为止了)
JSX 支持一些稍微高级的用法,如:
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
ReactDOM.render(
element,
document.getElementById('root')
);
在任何地方使用 JSX:
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
元素 🔗
上面有说到 React element
(元素),元素的概念与组件不同:元素是组件的组成部分。
元素渲染 🔗
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
显然,它掌控了 DOM 中一个 ID 为 root 的节点,并往里面插入了元素。
元素更新 🔗
已创建的元素是无法更新属性的。因此,如果要改变它,只能够重新创建并渲染一次。
然而,托虚拟 DOM 的福,重新渲染并不代表重新渲染整个 DOM,React 会查找并只更新有改变的节点。
但是一般不回这么做。因为有一点很重要:在设计一个元素的时候就要考虑到它在所有状态下的表现。这个其实在其它框架下也是一样的。
组件 🔗
React 是组件化框架,因此组件是组成一个应用的基础。组件的特点:独立、可重用。
组件有两种定义方法:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
或者:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
组件渲染 🔗
一个简单的例子:
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
可以看到元素组成了组件,组件又组成了元素,最后渲染在 DOM 上的是元素。
这个跟 Vue 很像了,区别是 Vue 没有区分所谓的“元素”跟“组件”,通通都是组件。
需要注意的是,在 React 世界中有个约定:自定义控件以大写字母打头。这是为了跟 HTML 元素有所区分。
组件使用与拆解 🔗
一个简单的例子:
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')
);
需要注意的是,组件只能有一个根节点。(如例子中的 3 个 Welcome
必须包裹在 div
中)
参数只读 🔗
简单地说,React 不允许在控件内修改参数(包括值的修改以及对象修改)。允许修改的称之为“状态”(约等于 Vue 中的 component data)
状态管理与生命周期 🔗
添加状态管理 🔗
组件的更新依赖于状态,因此需要实时更新的组件应在其内部建立状态管理机制(低耦合高内聚)。
需要状态管理机的组件,必须使用 ES6 方式声明,如:
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()};
}
// 组件渲染到 DOM 后调用
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')
);
整个流程很简单清晰了:
ReactDOM 渲染Clock
,并对 state 做第一次初始化
render
方法被调用,插入 DOM
componentDidMount
方法被调用,计时器启动,tick
每秒钟执行一次
每次 tick
执行都调用 setState
方法去更新状态,这样 React 就知道需要更新 DOM 了
当组件被从 DOM 移除后,componentWillUnmount
执行
正确使用状态 🔗
直接更改 state 属性是不会触发 UI 更新的。因此,有一些规则需要遵守。
不直接修改状态 🔗
在组件内进行修改状态操作,使用 setState
方法:
// Wrong
this.state.comment = 'Hello';
// Correct
this.setState({comment: 'Hello'});
关于异步更新 🔗
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
状态合并 🔗
在进行 setState
的时候,只关心需要更改的属性即可,没有传入的属性会被保留。就好像新的状态被“合并”进入旧状态一样。
数据流 🔗
在 React 世界,组件与组件之间的状态传递是单向的,传值的方式就是将 state 当做 prop 传给子组件。
事件处理 🔗
跟 DOM 操作很像,区别:
事件命名使用驼峰式 直接向 JSX 中传入方法 不支持return false
操作
例:
// DOM
<button onclick="activateLasers()">
Activate Lasers
</button>
// React
<button onClick={activateLasers}>
Activate Lasers
</button>
// A prevent default sample
function ActionLink() {
function handleClick(e) {
e.preventDefault();
console.log('The link was clicked.');
}
return (
<a href="#" onClick={handleClick}>
Click me
</a>
);
}
// A class sample
class Toggle extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: true};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<button onClick={this.handleClick}>
{this.state.isToggleOn ? 'ON' : 'OFF'}
</button>
);
}
}
注意:这个 e
是 React 封装过的,但遵循 W3C 标准,因此无需做浏览器差异化处理。
另外,this.handleClick.bind
方法是为了保证在 onClick
中调用了正确的 this
,但使用箭头函数可以避免这个累赘的方法:
class LoggingButton extends React.Component {
// This syntax ensures `this` is bound within handleClick.
// Warning: this is *experimental* syntax.
handleClick = () => {
console.log('this is:', this);
}
render() {
return (
<button onClick={this.handleClick}>
Click me
</button>
);
}
}
条件渲染 🔗
例子:
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>
);
}
行内判断 🔗
例子:
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
因此,当 unreadMessages.length > 0
为真时,后面的 JSX 会被渲染,反则不会。
除此以外还有三元表达式:
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
</div>
);
}
阻止渲染 🔗
在组件的 render
方法内 return null
会阻止组件的渲染,但是其生命周期不受影响。
循环 🔗
一个简单的例子:
const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =>
<li>{number}</li>
);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
循环组件 🔗
一个列表组件示例:
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')
);
注意,这里对列表项添加了一个 key
属性。
Key 🔗
Key
是 React 用来追踪列表项的一个属性。跟 angular 以及 vue 中 track-by
的概念一样。
如果列表项没有唯一标识,也可以用索引作为 key (不推荐):
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
注意:Key
只能直接在数组循环体内定义。如:
function ListItem(props) {
const value = props.value;
return (
// Wrong! There is no need to specify the key here:
<li key={value.toString()}>
{value}
</li>
);
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Wrong! The key should have been specified here:
<ListItem value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
文章来源:
Author:wxsm's blog
link:https://blog.wxsm.space/p/react-note-basic