React核心概念
JSX
JSX是一个JavaScript的语法扩展,在jsx语法中,可以在大括号内放置任何有效的JavaScript表达式。
- ReactDom会在渲染所有输入内容之前,默认进行转义,所有的内容在渲染之前都被转换成了字符串。所以JSX可以
防止XSS(注入攻击)。 - Babel会把JSX转译为一个名为
React.createElement()函数调用。比如:
const element = (
<h1 className="greeting">
hello, world!
</h1>
)
等价于:
const element = React.createElement(
"h1",
{
className: "greeting"
},
"hello, world!"
)
函数组件和class组件
class组件
生命周期方法
class组件中具有生命周期方法,常见的生命周期方法有:construcor, render, componentDidMount, componentDidUpdate, componentWillUnmount。
state状态管理
class组件中使用state来进行内部状态管理,使用setState要注意:
-
必须要使用setState来修改state状态
-
state的更新可能是异步的,也可能是同步的。如果要使用上一次的状态来计算新的状态,可以让setState接受一个函数而不是一个对象。
this.setState((state,props) => {
counter: state.counter + props.increment
})
- state的更新可能会被合并
函数组件
React 16.8版本支持了React hooks,函数组件内部也可以维护管理状态。
事件处理
React中元素的事件处理与Dom的事件处理相似,不同之处在于:
- React事件的命名采用驼峰命名,而不是纯小写
- 使用JSX语法时需要传入一个函数,而不是一个字符串
- 不能通过return false的方式阻止默认行为,必须要显式使用preventDefault
- 在为事件处理函数传递额外的参数时,事件对象e会被作为第二个参数传递
表单、受控组件与非受控组件
在HTML中,表单元素通常自己维护state,并根据用户输入进行更新。
在React中,可变状态(mutable state)通常保存在组件的state属性中,并且只能通过使用setState来更新。
把二者结合,React的state成为唯一的数据源,而渲染表单的React组件还控制着用户输入过程中表单发生的操作。被React以这种方式控制取值的表单输入元素就叫做受控组件。
常见的表单元素(受控组件)
- input
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange}/>
</label>
<input type="submit" value="提交" />
</form>
- textarea
<form onSubmit={this.handleSubmit}>
<label>
文章:
<textarea value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
- select
<form onSubmit={this.handleSubmit}>
<label>
选择你喜欢的风味:
<select value={this.state.value} onChange={this.handleChange}>
<option value="grapefruit">葡萄柚</option>
<option value="lime">酸橙</option>
<option value="coconut">椰子</option>
<option value="mango">芒果</option>
</select>
</label>
<input type="submit" value="提交"/>
</form>
- 当具有多个输入时,我们可以给每个input元素添加name属性,并让处理函数根据event.target.name的值选择要执行的操作。
class Reservation extends React.Component {
constructor(props) {
super(props);
this.state = {
isGoing: true,
numberOfGuests: 2
};
this.handleInputChange = this.handleInputChange.bind(this);
}
handleInputChange(event) {
const target = event.target;
const value = target.name === 'isGoing' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
render() {
return (
<form>
<label>
参与:
<input
name="isGoing"
type="checkbox"
checked={this.state.isGoing}
onChange={this.handleInputChange} />
</label>
<br />
<label>
来宾人数:
<input
name="numberOfGuests"
type="number"
value={this.state.numberOfGuests}
onChange={this.handleInputChange} />
</label>
</form>
);
}
}
非受控组件
使用非受控组件来处理表单数据时,表单数据将交由DOM节点来处理。
要编写一个非受控组件,可以使用ref来从DOM节点中获取表单数据。
在非受控组件中,经常希望赋予组件一个初始值,但是不去控制该值的后续更新,可以指定一个defaultValue属性,而不是value。在一个组件已经挂载之后去更新defaultValue的值,不会造成DOM上值的任何更新。
<input type="text"/>,<input type="select"/>,<input type="textarea"/>支持defaultValue<input type="checkbox"/>,<input type="radio"/>支持defaultChecked
- 使用非受控组件来接收一个input输入的值
class NameForm extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.input = React.createRef();
}
handleSubmit(event) {
alert('A name was submitted: ' + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Name:
<input defaultValue="Bob" type="text" ref={this.input} />
</label>
<input type="submit" value="Submit" />
</form>
);
}
}
- 文件输入
在React中,文件输入(即<input type="file"/>)始终是一个非受控组件。因为它的值只能由由用户设置,而不能通过代码控制。应该使用File Api与文件进行交互。
class FileInput extends React.Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.fileInput = React.createRef();
}
handleSubmit(event) {
event.preventDefault();
alert(
`Selected file - ${this.fileInput.current.files[0].name}`
);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
Upload file:
<input type="file" ref={this.fileInput} />
</label>
<br />
<button type="submit">Submit</button>
</form>
);
}
}
状态提升
在React中,将多个组件中需要共享的state向上移动到它们的最近共同父组件中,便可以实现共享state,即所谓的状态提升.
组件复用
React推荐使用组合而非继承来实现组件间的代码复用。
包含关系
在Sidebar,Dialog等展现通用容器的组件中,往往无法提前知晓它们子组件的具体内容。可以使用props.children来将它们的子组件传递到渲染结果中。
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
还可以通过传入其它的props属性来复用组件,比如预留位置:
function SplitPane(props) {
return (
<div className="SplitPane">
<div className="SplitPane-left">
{props.left}
</div>
<div className="SplitPane-right">
{props.right}
</div>
</div>
);
}
function App() {
return (
<SplitPane
left={
<Contacts />
}
right={
<Chat />
}
/>
);
}
特例关系
有些时候,我们会把某些组件看成是其它组件的特殊实例,如:WelcomeDialog 可以说是 Dialog 的特殊实例,也可以用组合来实现。
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
</FancyBorder>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!"
/>
);
}
思考:我们应该如何构建一个应用?
这部分内容在React官网中被命名为React哲学,我个人认为是React中最为重要的部分,因为它是一种对于思考的引导,引导你具有设计组件或应用的能力。
1. 对照设计稿,将设计好的UI划分为组件层次
根据单一功能原则来判定组件的范围,即一个组件原则上只能负责一个功能,如果需要负责多个功能,就应该考虑将它拆分为更小的组件。
实践中,UI结构一般与数据模型一一对应。
划分好 UI组件层次后,我们一般可以得到如下的层级结构:
-
FilterableProductTable-
SearchBar -
ProductTableProductCategoryRowProductRow
-
2. 用React创建一个静态版本
在划分好组件层级之后,我们就可以开始编写对应的应用了。React建议先编写一个不包含交互功能的UI,最好将渲染UI和添加交互这两个过程分开。
可以选择自上而下或自下而上的方式来构建应用。
-
对于比较简单的应用,使用自上而下的方式更方便。
-
对于较为大型的应用,推荐自下而上的方式,并在编写低层级组件的同时为其编写测试。
3. 确定UI state的最小(且完整)表示
编写好静态版本之后,我们要添加交互功能了,想要是UI具备交互功能,需要具有触发基础数据模型改变的能力,React通过实现state来改变基础数据模型。
判断一个数据是否应该属于state:
-
该数据是否由父组件通过props传递来的?
-
是否随着时间的推移而保持不变?
-
能否通过其它state或props计算出该数据的值?
如果一个数据符合上面三项中的任意一项,则这个数据不应该是state。
4. 确定state放置的位置
我们现在已经知道有哪些state了。接下来我们要做的就是把state放置在正确的位置。这一部分其实对应的是状态提升。
我们判断一个state应该放在哪里,可以通过这几个步骤:
-
找到根据这个state进行渲染的所有组件
-
找到它们的共同所有者
-
该共同所有者组件或比它层次更高的组件应该拥有该state
-
如果找不到合适的位置来存放该state,可以直接创建一个新的组件来存放该state,并将这一新组件置于高于共同所有者组件层级的位置。
在我们的实际开发中,一般不会存在两级以上的组件共用状态,如果出现了多个不同层级的组件共用同一个状态的情况,我们可以使用Redux、Provider等方式来在最上层存储共用状态。
5. 添加反向数据流
反向数据流其实就是:让低层级组件能够更新高层级组件中的state。
我们一般通过父组件给子组件传递一个回调函数,子组件调用该回调函数的方式来实现子组件对于父组件state的更新
结语
以上就是React的所有核心内容了。当然,React能够作为一个主流的前端框架,其内涵不可能仅仅只有这些,还有诸如高阶组件、协调、hooks等较为复杂的内容,以及Redux、Router等配套库,我们会在后面的React专栏内容中一一分析。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。