JSX
const element = <h1>Hello, world!</h1>;
const component = <Component />
这种类似HTML又与JS相结合的代码称之为JSX,实际上JSX会倍babel转换成React.createElement
,在babel官网可以得出转换结果。
而React.createElement
函数的返回值对象被我们称作虚拟DOM
使用
JSX 中可以嵌入表达式
const name = 'Josh Perez';
const element = <h1>Hello, {name}</h1>;
JSX 本身也是一个表达式
function formatName(user) {
return user.firstName + ' ' + user.lastName;
}
function getWorld (world) {
if (world) {
return <h1>Hello,World!</h1>;
} else {
return <h1>GG!{formatName(user)}!</h1>;
}
}
JSX 特定属性
const element = <div tabIndex="0"></div>;
const element = <img src={user.avatarUrl}></img>;
组件
React中分别有函数组件与 class 组件,函数组件没有内部状态和生命周期,但目前可以通过Hook实现类似的效果。
React 非常灵活,但它也有一个严格的规则:所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
纯函数:不会尝试更改入参,且多次调用下相同的入参始终返回相同的结果。
// 函数组件
const Welcome = (props) => <h1>Hello, {props.name}</h1>
// class组件
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
以下以class组件为主
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')
);
状态
类组件中通过this.setState
修改数据,直接修改state,组件并不会重新触发render()。
// 错误
this.state.comment = 'Hello';
// 正确
this.setState({comment: 'Hello'});
State 的更新可能是异步的
出于性能考虑,React 可能会把多个 setState()
调用合并成一个调用。
因为 this.props
和 this.state
可能会异步更新,所以不要依赖他们的值来更新下一个状态。
this.setState({
count: 1 // 假设原始值为0
})
console.log(this.state.count) // 输出0而不是1
想要在更新操作完成后执行操作,可传入回调函数。
this.setState({ count: 1 }, () => {
console.log(this.state.count) // 输出1
})
如果依赖于之前的State,可以让 setState()
接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数。
// Wrong
this.setState({
counter: this.state.counter + this.props.increment
});
// Correct
this.setState((state, props) => ({
counter: state.count + props.increment
}))
事实上,在合成事件和组件的生命周期中setState
是异步的;而在原生事件和定时器中setState
是同步的。
这是因为,React内部维护了一个标识:isBatchingUpdates
。在合成事件和组件的生命周期中,该值为true
,那么setState
会被缓存进队列,最后才批量更新;而在原生事件和定时器中,该值为false
,调用setState
时会直接同步更新。
生命周期
事件
React中的event
是合成事件,不需要担心跨浏览器的兼容性问题。
// 方法一 避免this丢失
constructor() {
this.handlerClick = this.handlerClick.bind(this)
}
// 方法二 使用箭头函数,初始化时已确认this,因每次渲染均会创建新函数,可能会有性能问题
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
// 方法三 public class fields (公有实例字段可以在基类的构造过程中(构造函数主体运行前)使用Object.defineProperty添加)
class Btn extends React.component {
handlerClick = (e) => {
console.log(this)
}
render() {
return (
<button onClick={this.handlerClick}>
Click me
</button>
)
}
}
列表 & Key
key 帮助 React 识别哪些元素改变了,比如被添加或删除。当元素没有确定 id 的时候,万不得已可以使用元素索引 index 作为 key。
数组元素 中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值:
深度解析使用索引作为 key 的负面影响 深入解析为什么 key 是必须的
function NumberList(props) {
const numbers = props.numbers
return (
<ul>
{numbers.map((number) => (
<ListItem key={number.toString()} value={number} />
))}
</ul>
)
}
表单
React表单元素分为受控组件和非受控组件
使 React 的 state 成为“唯一数据源”,渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
// ...
constructor(props) {
super(props)
this.state = { value: '' }
}
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
// ...
render() {
return <input type="text" value={this.state.value} onChange={this.handleChange}/>
}
在 HTML 中,表单元素(如<input>
、 <textarea>
和 <select>
)之类的表单元素通常自己维护 state,并根据用户输入进行更新,这种称之为“非受控组件”
没有设置value或者设置为null的是一个非受控组件,对于非受控的input组件,用户的输入会直接反映在页面上,如果想要给组件设置一个非空的初始值,可以使用defaultValue属性。
render() {
return <input type="text" value={null}/>
}
状态提升
多个组件需要反映相同的变化数据,这时我们建议将共享状态提升到最近的共同父组件中去。
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。