React简介
React是一个用于构建用户界面的JavaScript库,主要有以下几个特点:
- 声明式设计--React采用声明范式,可以轻松描述应用
- 高效--React通过对DOM的模拟,最大限度地减少与DOM的交互
- 灵活--React可以与已知的库或框架很好地配合
- JSX--JSX是JavaScript语法的扩展。React开发不一定使用JSX,但建议使用
- 组件--通过React构建组件,是的代码更加容易得到复用,能够更好地应用在大项目的开发中
- 单向响应的数据流--React实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统的数据绑定更简单
React安装
- 使用create-react-app快速构建React开发环境
创建React App是开始构建新的React单页应用程序的最佳方式。它设置您的开发环境,以便您可以使用最新的JavaScript功能,提供一个很好的开发人员体验,并优化您的应用程序的生产。
npm install -g create-react-app
create-react-app hello-world
cd hello-world
npm start
ReactDOM.render()
ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
ReactDOM.render( <h1>Hello, world!</h1>, document.getElementById('example') );
React JSX
JSX是一个看起来很像XML的JavaScript语法扩展,我们不是必须使用JSX,但是JSX有以下优点:
- JSX执行更快,因为它在编译为JavaScript代码后进行优化
- 它是类型安全的,在编译过程中就能发现错误
- 使用JSX编写模板更加方便快捷
JSX的基本语法规则:<u>遇到 HTML 标签(以 <
开头),就用 HTML 规则解析;遇到代码块(以 {
开头),就用 JavaScript 规则解析。</u>
警告:
由于JSX比HTML更接近JavaScript,React DOM使用
camelCase
属性命名约定而不是HTML属性名称。
/**使用JXS */
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
React组件和属性
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')
);
上面的代码中,变量App就是一个组件,所有的组件类都必须有自己的render方法,用于输出组件。
注意:组件名称必须使用大写,比如<div />就是一个HTML标签,而
<App />
表示一个组件,并且要求组件类只能有一个顶层标签,否则会报错。
组件的属性可以在组件类的 this.props
对象上获取,上面的例子中,name属性是通过this.props.name来获取的。
注意:在添加属性时, class 属性需要写成 className ,for 属性需要写成 htmlFor ,这是因为 class 和 for 是 JavaScript 的保留字。
this.props.children
this.props
对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children
属性。它表示组件的所有子节点。
var NotesList = React.createClass({ render: function() { return ( <ol> { React.Children.map(this.props.children, function (child) { return <li>{child}</li>; }) } </ol> ); } }); ReactDOM.render( <NotesList> <span>hello</span> <span>world</span> </NotesList>, document.body );
上面代码的 NoteList
组件有两个 span
子节点,它们都可以通过 this.props.children
读取,运行结果如下。
这里需要注意, this.props.children
的值有三种可能:如果当前组件没有子节点,它就是 undefined
;如果有一个子节点,数据类型是 object
;如果有多个子节点,数据类型就是 array
。所以,处理 this.props.children
的时候要小心。
React 提供一个工具方法 React.Children
来处理 this.props.children
。我们可以用 React.Children.map
来遍历子节点,而不用担心 this.props.children
的数据类型是 undefined
还是 object
。更多的 React.Children
的方法,请参考官方文档。
组件状态和生命周期
我们通常使用两种数据来控制一个组件:props和state。props是在父组件中指定的,而且一经指定,在被指定的组建的生命周期中则不改变,对于需要改变的数据,我们需要使用state来处理。
通常情况下,我们需要在constructor中初始化state,然后在需要修改的时候调用setState()方法。
/**加了状态之后的定时器 */
class Clock extends Component {
constructor(props) {
super(props);
this.state = {date: new Date()}; // 设置初始状态
}
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
componentWillUnmount() { //在组件从 DOM 中移除的时候立刻被调用
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>
);
}
}
上面的例子,我们主要是通过以下几步来实现的:
1)当
<Clock />
传递给ReactDOM.render()
React时,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知道状态已更改,并render()
再次调用方法来了解应该在屏幕上显示什么。这一次,this.state.date
在render()
方法中会有所不同,因此渲染输出将包括更新的时间。React相应地更新DOM。5)如果
Clock
组件从DOM中删除,React会调用componentWillUnmount()
生命周期钩子,以便定时器停止。
如何正确使用状态
不要直接修改状态
例如,这将不会重新渲染组件:
// Wrong
this.state.comment = 'Hello';
相反,请使用setState()
:
// Correct
this.setState({comment: 'Hello'});
唯一可以指定this.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
}));
我们使用上面的箭头函数,但它也可以与常规函数一起使用:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
状态更新已合并
每次调用setState()
时,React都会将您提供的对象合并到当前状态。
例如,您的状态可能包含几个独立变量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后您可以通过单独的setState()
调用独立地更新它们:
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
。
组件的生命周期
组件的生命周期可分成三个状态:
- Mounting: 已插入真实DOM
- Updating: 正在被重新渲染
- Unmounting: 已移出真实DOM
生命周期的方法:(详细说明,可以参考官方文档。)
- componentWillMount 在渲染前调用。
- componentDidMount : 在第一次渲染后调用。之后组件已经生成了对应的DOM结构,可以通过this.getDOMNode()来进行访问。 如果你想和其他JavaScript框架一起使用,可以在这个方法中调用setTimeout, setInterval或者发送AJAX请求等操作(防止异部操作阻塞UI)。
- componentWillReceiveProps 在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
-
shouldComponentUpdate 返回一个布尔值。在组件接收到新的props或者state时被调用。在初始化时或者使用forceUpdate时不被调用。
可以在你确认不需要更新组件时使用。 - componentWillUpdate在组件接收到新的props或者state但还没有render时被调用。在初始化时不会被调用。
- componentDidUpdate 在组件完成更新后立即调用。在初始化时不会被调用。
- componentWillUnmount在组件从 DOM 中移除的时候立刻被调用。
处理事件与条件渲染
处理事件
使用React元素处理事件与处理DOM元素上的事件非常相似。有一些语法差异:
- React事件使用camelCase命名,而不是小写命名。
- 使用JSX你传递一个函数作为事件处理程序,而不是一个字符串。
另一个区别是,你不能返回false
以防止React中的默认行为。您必须preventDefault
明确调用。
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')
);
你必须注意this
在JSX回调中的意思。在JavaScript中,类方法默认不绑定。如果你忘记绑定this.handleClick
并把它传递给onClick
,在函数实际被调用时this
将会报undefined
的错误。
条件渲染
React中的条件渲染与JavaScript中的条件工作方式相同。使用类似if
的JavaScript运算符或条件运算符来创建表示当前状态的元素,并让React更新UI以匹配它们。
/**条件渲染 */
class LoginControl extends 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>
)
}
}
function LoginButton(props) {
return (
<button onClick={props.onClick}>
Login
</button>
);
}
function LogoutButton(props) {
return (
<button onClick={props.onClick}>
Logout
</button>
);
}
function UserGreeting(props) {
return <h1>Welcome back!</h1>;
}
function GuestGreeting(props) {
return <h1>Please sign up.</h1>;
}
function Greeting(props) {
const isLoggedIn = props.isLoggedIn;
if (isLoggedIn) {
return <UserGreeting />;
}
return <GuestGreeting />;
}
ReactDOM.render(
<LoginControl />,
document.getElementById('root')
);
虽然声明变量和使用if
语句是有条件地呈现组件的一种很好的方式,但有时您可能想使用较短的语法。有几种方法来内联JSX中的条件,如下所述。
逻辑运算符
您可以在JSX中嵌入任何表达式,将它们括在大括号中。这包括JavaScript逻辑&&
运算符。它可以方便地有条件地包括一个元素:
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将忽略并跳过它。
带条件运算符的If-else
用于有条件地内联元素的另一种方法是使用JavaScript条件运算符condition ? case1 : case2
,当条件为true时,执行case1,否则执行case2。
防止组件呈现
在极少数情况下,您可能希望组件隐藏自身,即使它由另一个组件呈现。要做这个返回null
而不是其渲染输出。
在下面的例子中,根据<WarningBanner />
被调用的prop的值来渲染warn
。如果prop的值为false
,则组件不呈现:
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')
);
列表和Key值
列表
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')
);
上述实例显示1到5之间的数字的项目符号列表。但是运行此代码时,将会收到一条警告,指出应为列表项提供一个键。“key”是创建元素列表时需要包含的特殊字符串属性。如下:
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}> //为列表项指定key值
{number}
</li>
);
return (
<ul>{listItems}</ul>
);
}
const numbers = [1, 2, 3, 4, 5];
ReactDOM.render(
<NumberList numbers={numbers} />,
document.getElementById('root')
);
Key值
key帮助确定哪些项目已更改,已添加或已删除。应该给数组中的元素赋予key以给元素一个稳定的身份。
选择key的最好方法是使用一个字符串,它在其兄弟之间唯一标识一个列表项。通常,您会使用数据中的ID作为键:
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
);
使用键提取组件:
如果提取一个ListItem
组件,则应该将该key保存<ListItem />
在数组中的元素上,而不是<li>
其ListItem
本身的根元素上。
正确使用:
function ListItem(props) {
// Correct! There is no need to specify the key here:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// Correct! Key should be specified inside the array.
<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')
);
一个好的经验法则就是map()
调用中的元素需要Key值。
key值在兄弟姐妹中唯一
数组中使用的key在其兄弟之间应该是唯一的。但是,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的key:
function Blog(props) {
const sidebar = (
<ul>
{props.posts.map((post) =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
);
const content = props.posts.map((post) =>
<div key={post.id}>
<h3>{post.title}</h3>
<p>{post.content}</p>
</div>
);
return (
<div>
{sidebar}
<hr />
{content}
</div>
);
}
const posts = [
{id: 1, title: 'Hello World', content: 'Welcome to learning React!'},
{id: 2, title: 'Installation', content: 'You can install React from npm.'}
];
ReactDOM.render(
<Blog posts={posts} />,
document.getElementById('root')
);
运行结果:
表单
用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props
读取。
/**表单 */
class NameForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSumbit = this.handleSumbit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSumbit(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>
)
}
}
ReactDOM.render(
<NameForm />,
document.getElementById('root')
);
上面代码中,文本输入框的值,不能用 this.props.value
读取,而要定义一个 onChange
事件的回调函数,通过 event.target.value
读取用户输入的值。textarea
元素、select
元素、radio
元素都属于这种情况,更多介绍请参考官方文档。