这是别眨眼学前端的第 001 篇文章,别眨眼系列文章的主旨在于能够快速的回顾某个库/框架或是某种编程语言的基本知识点,于我而言作用类似于相对详细一点的复习大纲,用于快速回顾记忆,标题命名来源于 苹果 的视频 Don't blink
这一篇我们来快速回顾一下 React,本文是基于 React 官方文档 学习编写而成,可能会有描述不够清楚的地方,可自行参考原文
有关 React 全家桶的其余相关文章,可以查看以下链接,会持续更新
JSX
是 JavaScript 的一种语法扩展,在 React 中 JSX
用于描述 UI 看起来是什么样子的,JSX
产生出 React “元素”,之后会转换成 DOM 渲染在页面上
基本用法,
我们可以在 JSX
中使用 JavaScript 表达式 ,使用 {}
将 JavaScript 表达式包裹起来使用,
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
表达式会变成常规的 JavaScript 对象,这意味着我们可以在 if
语句和 for
循环中使用 JSX
,同时也可以将其声明为变量当做函数参数或者函数返回值,
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user.name)}!</h1>;
} else {
return <h1>Hello, Stranger.</h1>;
}
}
同时,JSX
为 React 的函数 React.createElement(component, props, ...children)
的语法糖,
以下 JSX
代码,
<MyButton color="blue" shadowSize={2}>
Click Me
</MyButton>
会被编译为,
React.createElement(
Mybutton,
{color: 'blue', shadowSize: 2},
'Click Me'
)
当没有子元素的时候,我们可以使用自闭合标签的书写方式,
<div className="sidebar" />
会被编译为,
React.createElement(
'div',
{className: 'sidebar'},
null
)
除了基本的引用方式之外,我可以使用对象属性值得方式来引用组件,这非常适用于在一个单独模块中导出多个组件,
const MyComponents = {
DatePicker: function DatePicker(props) {
return <div>Imagine a {props.color} datepicker here.</div>;
}
}
function BlueDatePicker() {
return <MyComponents.DatePicker color="blue" />;
}
自定义组件必须以大写字母开始,
当一个元素或组件以小写字母开始的,它会和内置的组件比如 <div>
或是 <span>
一样以字符串的形式,'div'
或 'span'
传入 React.createElement
中,而以大写字母开头的自定义组件如 <Foo />
会被编译为 React.createElement(Foo)
并且同时会在你的 JS
文件中引用或者声明该自定义组件
在使用 JSX
时有以下几种方式去声明一个组件的 porps
,
JavaScript 表达式,
我们可用 JavaScript 表达式来当做属性值,使用 {}
来包裹 JavaScript 表达式,
<MyComponent foo={1 + 2 + 3 + 4} />
对于组件 MyComponent
来说,它的属性 props.foo
的值为 10
,
if
语句与 for
循环在 JavaScript 中并非表达式,所以它们并不能在 JSX
中直接使用,一般我们这样使用,
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>;
}
字符串字面量,
可以使用字符串字面量来当做组件的属性值,下面两个 JSX
的表达式结果是一样的,
<MyComponent message="hello world" />
<MyComponent message={'hello world'} />
属性值默认为 True
当我们在组建中传递一个没有值的属性时,它的默认值为 true
,以下两个 JSX
表达式的结果是一样的,
<MyTextBox autocomplete />
<MyTextBox autocomplete={true} />
在 JSX
表达式中都会拥有一对闭合标签,闭合标签包裹的内容都会被传递进一个特殊的属性中:props.children
,通常有以下几种方式将值传递给 props.children
,
字符串字面量
<MyComponent>Hello world!</MyComponent>
组件 <MyComponent>
的属性 props.children
值为字符串 "Hello world!"
JSX
会自动移除空白行,内容开始结束的空格
JSX Childern
同样,闭合标签内的子组件也会被传递给 props.children
,
<MyContainer>
<MyFirstComponent />
<MySecondComponent />
</MyContainer>
JavaScript 表达式
同样,闭合标签内的 JavaScript 表达式也会被传递给 props.children
,一下两种 JSX
表达式的结果是一样的,
<MyComponent>foo</MyComponent>
<MyComponent>{'foo'}</MyComponent>
函数表达式
function ListOfTenThings() {
return (
<Repeat numTimes={10}>
{(index) => <div key={index}>This is item {index} in the list</div>}
</Repeat>
);
}
function Repeat(props) {
let items = [];
for (let i = 0; i < props.numTimes; i++) {
items.push(props.children(i));
}
return <div>{items}</div>;
}
元素是 React app 中最小的组成模块,一个元素可以用以下方式描述,
const element = <h1>hello, world</h1>;
与浏览器 DOM 元素不同,创建或修改 React 元素的开销是极低的,我么使用 ReactDOM.render()
将 React 元素渲染至 DOM 节点中,
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElmentById('root')
);
React 元素是不可变的,这意味着当我们创建出一个元素后,就不能改变他的子元素或是属性,它代表这某个时刻的 UI 所呈现出来的样子,
改变 UI 的方法之一是创建出一个新的元素并将它传递给 ReactDOM.render()
方法,并再次执行,
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
通常 React app 中只会调用一次
ReactDOM.render()
方法,我们会在之后的章节中介绍怎样将 React 元素封装成为有状态的组件并通过状态的变化来实现 UI 的变化
组件允许我们拆分 UI 为独立的可复用的小块,概念上来讲,组件就像JavaScript 中的函数,它允许传递参数 (React 中称之为 Props
),并且返回 React 元素来表示什么应该显示在页面上,
定义一个组件最简单的方式就是使用 JavaScript 函数:
function Welcome(props) {
return <h1>Hello, {porps.name}</h1>;
}
上述函数是一个有效的组件,因为它传递了一个包含数据的 props
对象参数以及返回了 React 元素,我们称之为功能组件 (functional) 因为它就是 JavaScript 中的函数字面量 (literally JavaScript functions),
同样你可以使用 ES6 class 来定义一个组件,
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
在这之前,我们遇到过一个 React 元素代表 DOM 标签的,
const element = <div />;
当然,一个 React 元素也可以代表一个自定义组件,
const element = <Welcom name="Sara" />;
当 React 遇到一个代表着自定义组件的元素的时候,会将 JSX
的属性作为一个单独的对象传递给该组件,我们称之为 props
,
下面代码中,结果是将 ”Hello,Sara” 渲染在了页面上:
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" />
参数代表需要渲染的组件,
React 调用 Welcome
组件并将 {name: 'Sara'}
作为该组件的 props
,
Welcome
组件返回 <h1>Hello, Sara</h1>
元素,
React DOM 根据返回的元素去更新 DOM
当我们需要渲染一个组件多次的时候,可以这样编写,
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
需要注意的是,所有组件必须包裹在唯一的根元素下,所以在上述例子中,我们将所有的
Welcome
组件包裹在<div>
下
props
为只读React 遵守一条严格的规定:
所有的 React 组件相对于他们的 props
来说表现的都像是纯函数,在下节中我们会了解到 React 使用 state
来更新组件表现以便响应用户交互等
在这之前,如果我们想更新已渲染后的 UI ,能够使用的方法就是再次调用 ReactDOM.render()
来重新渲染所有的组件输出,如下代码模拟了时间的变化,
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
然而我们并不希望这么做,正如之前提到的,一般情况下一个 React 应用只会调用一次 ReactDOM.render()
,所以当我们需要动态更新组件的时候,我们需要使用 state
,
只有类组件拥有 state
属性,功能组件不具备 state
属性,我们可以通过以下几步将一个功能组件转换成一个类组件,
使用 ES6 class 语法来创建一个继承于 React.Component
的与功能组件同名的类
添加一个空方法 render()
将功能组件中的主体部分移入 render()
方法中
将之前使用到 props
的地方替换为 this.props
一个类似与上述例子的类组件如下,
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
接下来我们会使用 state
来替代 props
,继而使组件能够动态更新,
将 this.props
替换为 this.state
,
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
添加 class constructor
来初始化组件 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')
);
在使用 state
的时候有三点需要注意,
不要直接操作 state
,
// Wrong
this.state.comment = 'Hello';
若要更改 state
,应该使用 setState()
方法,
this.setState({comment: 'Hello'});
state
的更新可能是异步的
处于性能的考虑,React 也许会批量处理一些 setState()
而统一进行一次更新,因为 this.props
和 this.state
也许会异步更新,所有不应依赖当前值去计算下一个状态,如,
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
正确的方法是,给 setState()
传递一个函数,这个函数接受两个参数,第一个参数表示上一个状态值,第二参数表示当前的 props
,
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
state
的更新会合并
当调用了 setState()
,React 会合并传入的更新的对象进当前的 state
中,例如,
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
// state 为 {posts: esponse.posts, comments: []}
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
// state 为 {posts: esponse.posts, comments: response.comments}
});
}
React 中的事件处理和在原生 DOM 中事件处理是极为相似的,只是有以下细微的差别,
React 事件使用驼峰命名法,而非全部小写,
在使用 JSX
时,传递一个函数当做事件处理程序,而非一个字符串
比如,在 HTML 会这样编写,
<button onclick="activateLasers()">
Activate Lasers
</button>
而在 React 中是这样的,
<button onClick={activateLasers}>
Activate Lasers
</button>
另一个不同点在与,在 React 中,不能使用 return 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>
);
}
在 React 中不应使用 addEventListener
来给 DOM 元素添加事件监听,一般会在初始化渲染的时候添加监听器,如下,
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>
);
}
}
ReactDOM.render(
<Toggle />,
document.getElementById('root')
);
对于表单组件来说,比如 <input>
, <textarea>
和 <option>
,和原生组件最大的区别表单组件会通过用户的交互来变化,表单控件为管理表单响应用户交互提供了接口
React 中的表单控件分为两种类型:
受控组件 (Controlled Components)
非受控组件 (Uncontrolled Components)
受控组件会提供一个 value
属性,该组建并不会维护自身内部的状态,该组件的渲染纯粹基于它的属性,
render() {
return <input type="text" value="Hello" />
}
用户输入并不会影响已渲染的元素,因为 React 已经将其的 value
属性声明为 Hello
,如果想让控件值随用户输入而改变,我们需要使用 onChange
事件,
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {value: ""};
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
return (
<div>
<input type="text"
placeholder="Hello!"
value={this.state.value}
onChange={this.handleChange} />
</div>
);
}
}
ReactDOM.render(<Form />, document.getElementById('root'));
不提供 value
属性的表单空间我们称之为非受控组件,
render() {
return <input type="text" />
}
上述例子中我们渲染了一个空值 <input>
控件,用户的任何输入都会直接翻译在已渲染的元素中,受控组级自己管理自己的状态,
如果想要给非受控组件一个初始化默认值,可以使用 defaultValue
属性,
render() {
return <input type="text" defualtValue="hello" />
}
表单组件提供了一些受用户交互影响的属性,
value
, <input>
和 <textarea>
组件支持,
checked
, type
为 checkbox
和 radio
的 <input>
组件支持,
selected
, <option>
组件支持
表单组件允许通过使用 onChange
属性来监听它的变化,一下情况下会触发 onChange
属性,
当 <input>
或 <textarea>
的 value
发生改变,
当 <input>
的 checked
状态发生改变,
当 <option>
的 selected
状态发生改变
和 DOM
事件相同,onChange
属性支持所有原生组件,并且可以监听冒泡事件
当许多组件需要使用相同的状态时,将该状态提升至这些组件共有的最近的父级组件上,并用 props
传递给各个子组件,并使用回调函数的方式使得子组件能够改变父组件的状态
至此,别眨眼看 React 完结啦,
同时如果文章中有任何错误,欢迎大家指出,好的文章需要你的支持,谢谢