Appearance
工作已经好几年工作经验了,还没有用过react
呢,最近打算把react
官网刷一波,写写文章输出一下!
监介
React
是一个声明式,高效且灵活的用于构建用户界面的JavaScript
库。使用React
可以将一些简短、独立的代码片段组合成复杂的UI
界面,这些代码片段被称作组件。
React
并不是一个严格的MVVM
框架,而是一个基于组件化的视图库。React
是单向数据流:UI = render(data)
JSX模板语法
JSX称为JS的语法拓展,将UI与逻辑层耦合在组件里,用"{}"标识。
JSX 的优点之一是它能够提供更好的可读性和易于理解的代码结构。另一个优点是它允许开发人员更轻松地将 JavaScript 和 HTML 结合在一起,从而更容易创建动态的 UI 组件。JSX 的语法看起来类似于 HTML,但实际上它是一种语法糖,会被转译成 JavaScript 代码。
因为JSX语法上更接近JS而不是HTML,所以使用camelCase(小驼峰命名)来定义属性的名称;JSX里面的class变成了className
,而tabindex变成了tabIndex
。
JSX支持表达式
js
// 变量
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
// 方法
const user = {
firstName: 'Harper',
lastName: 'Perez'
};
const element = (
<h1>
Hello, {formatName(user)}!
</h1>
);
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
JSX指定属性
js
const element = <img src={user.avatarUrl}></img>;
JSX表示对象
js
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
// 等同于React.createElement
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
const element = {
type: 'h1',
props: {
className: 'greeting',
children: 'Hello, world!'
}
};
将JSX渲染为DOM
js
// 使用ReactDOM.render
const element = <h1>Hello, world</h1>;
ReactDOM.render(element, document.getElementById('root'));
// render只能代表当前时刻的状态
// 更新元素 只能再次 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); // 不建议多次render
props & state
组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。
组件
函数式组件
js
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
类组件
js
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
渲染组件
js
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" />;
ReactDOM.render(
element,
document.getElementById('root')
);
自定义组件使用大写字母开头
js
function Hello(props) {
// 正确! 这种 <div> 的使用是合法的,因为 div 是一个有效的 HTML 标签:
return <div>Hello {props.toWhat}</div>;
}
function HelloWorld() {
// 正确!React 知道 <Hello /> 是一个组件,因为它是大写字母开头的:
return <Hello toWhat="World" />;
}
组件的组合和拆分
有了组件可以在父级组件内多次引用
js
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
组件的拆分
js
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">
{props.author.name}
</div>
</div>
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
拆分后
js
function UserInfo(props) {
return (
<div className="UserInfo">
<img className="Avatar"
src={props.avatarUrl}
alt={props.name}
/>
<div className="UserInfo-name">
{props.name}
</div>
</div>
)
}
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">
{props.text}
</div>
<div className="Comment-date">
{formatDate(props.date)}
</div>
</div>
);
}
受控组件 与 非受控组件
受控组件:对某个组件状态的掌控,它的值是否只能由用户设置,而不能通过代码控制;
在HTML的表单元素中,它们通常自己维护一套state,并随着用户的输入自己进行UI上的更新,这种行为是不被我们程序所管控的。而如果将React里的state属性和表单元素的值建立依赖关系,再通过onChange事件与setState()结合更新state属性,就能达到控制用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
js
// input自身维护的状态,外界无法获取数据
class TestComponent extends React.Component {
render () {
return <input name="username" />
}
}
// 可以设置初始值
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = { username: 'test' };
}
render () {
return <input name="username" value={this.state.username} />
}
}
// 可以读取并设置初始值
class TestComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
username: "test"
}
}
onChange (e) {
console.log(e.target.value);
this.setState({
username: e.target.value
})
}
render () {
return <input name="username" value={this.state.username} onChange={(e) => this.onChange(e)} />
}
非受控组件:对应的,组件内的状态不由用户控制
js
// 如果不想关心表单元素的值是如何变化的,只想取值,可以使用ref
import React, { Component } from 'react';
export class UnControll extends Component {
constructor (props) {
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) => {
console.log('我们可以获得input内的值为', this.inputRef.current.value);
e.preventDefault();
}
render () {
return (
<form onSubmit={e => this.handleSubmit(e)}>
<input defaultValue="lindaidai" ref={this.inputRef} />
<input type="submit" value="提交" />
</form>
)
}
}
props
React 组件使用 props 来互相通信。每个父组件都可以提供 props 给它的子组件,从而将一些信息传递给它。Props 可能会让你想起 HTML 属性,但你可以通过它们传递任何 JavaScript 值,包括对象、数组和函数。 因为react是单向数据流,所以所有React 组件都必须像纯函数一样保护它们的 props 不被更改。
js
// 错误,要像纯函数一样幂等
function withdraw(account, amount) {
account.total -= amount;
}
state
如何避免多次render?看下下面的例子
js
// 使用props形式
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);
这样就会频繁的render,我们使用生命周期和类组件来解决
js
// 引用生命周期,根组件保留一个
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')
);
setState
js
this.setState({comment:'hihi'})
state更新可能是异步的
js
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
state更新会合并
js
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
componentDidMount() {
fetchPosts().then(response => {
// 相当于{post: response.posts, ...otherState}
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
}
单向数据流
state 只在当前的组件里生效,属于组件内的属性,重复实例化相同的组件,内部的内存地址也是不一样的;
合成事件
js
class App extends Component {
state = { val: 0 }
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 0
}
render() {
return (
<div onClick={this.increment}>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
setState是异步的所以在打印的时候输出的是更新前的值。
在生命周期中
js
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
在生命周期中更新,打印的时候输出的是更新前的值。
原生事件
js
class App extends Component {
state = { val: 0 }
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
setTimeout
js
class App extends Component {
state = { val: 0 }
componentDidMount() {
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出更新后的值 --> 1
}, 0)
}
render() {
return (
<div>
{`Counter is: ${this.state.val}`}
</div>
)
}
}
批处理
js
class App extends Component {
state = { val: 0 }
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<div onClick={this.batchUpdates}>
{`Counter is ${this.state.val}`} // 1
</div>
)
}
}
- setState 只在合成事件和生命周期中是“异步”的,在原生事件和 setTimeout 中都是同步的;
- setState的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的, 只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形式了所谓的“异步”, 当然可以通过第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果。
- setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次 setState , setState 的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时 setState 多个不同的值,在更新时会对其进行合并批量更新。
条件渲染
通常你的组件会需要根据不同的情况显示不同的内容。在 React 中,你可以通过使用 JavaScript 的 if
语句、&&
和 ? :
运算符来选择性地渲染 JSX。
if else渲染
js
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;
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')
);
与运算符 &&
js
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')
);
// 返回false的表达式,会跳过元素,但会返回该表达式
render() {
const count = 0;
return (
<div>
{ count && <h1>Messages: {count}</h1>}
</div>
);
}
三元运算符
js
render() {
const isLoggedIn = this.state.isLoggedIn;
return (
<div>
{isLoggedIn
? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />
}
</div>
);
}
列表渲染
你将依赖 JavaScript 的特性,例如 for
循环 和 array 的 map()
函数来渲染组件列表。
js
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,会warning a key should be provided for list items
// key可以帮助react diff,最好不用index作为key,会导致性能变差;
// 如果不指定显式的 key 值,默认使用索引用作为列表项目的 key 值;
总结
今天先肝这些,把一些比较基础简单的东西写了一下,基本上可以写一些简单的demo,估计能上手开发了。奈何没有练手的项目,只能自己捣鼓了。
参考
- react官网
- 还有一些课程的课件