[React]核心概念
本文是对React文档:核心概念部分的笔记,内容大致与文档相同。
文档链接
React哲学部分写的很好,务必要看
JSX
JSX是JS的语法扩展,配合react使用,为JS和HTML的混写
JSX支持在大括号({})中书写任何有效JS表达式
同时,JSX也是一个表达式
如下面的例子:
const name="josh perez"
const element=<h1>Hello,{name}</h1>
ReactDOM.render(
element,
document.getElementById("root")
);
在属性中嵌入表达式有两种方法:
//使用引号包裹的字面值
const element =<div tabIndex="0"></div>
//使用花括号包裹的表达式
const element=<img src={user.name}></img>
JSX的属性名称使用小驼峰命名,如class->className
React在渲染已经输入的内容前,会默认进行转义,所有的内容在渲染前已经被转换为字符串,可以有效防止XSS
JSX对象:
Babel将会把JSX转译为一个名为React.createElement()的函数调用
即:
const element=(
<h1 className="greet">
hello
</h1>
);
//与下面的写法相同
const element=React.createElement(
"h1",
{className:"greet"},
"hello"
);
//它事实上创建了一个简化的如下的对象:
const element={
type:"h1",
props:{
className:"greet",
children:"hello"
}
};
它们也被称为React元素,描述了希望在屏幕上看到的内容,React通过读取这些对象来使用他们构建DOM
元素渲染
元素是构成React应用的最小块
React元素是创建开销很小的普通对象。React DOM更新DOM来与React元素保持一致
React 根 DOM 节点
React 根 DOM 节点大概是类似这样的结构:
这个节点内的所有内容都由React DOM管理。
对于根DOM节点,使用ReactDOM.render()进行渲染
const element=<h1>hello</h1>
ReactDOM.render(element,document.getElementById("root"));//root就是根DOM节点的id值
React元素是不可改变对象,一旦被创建就不能修改。更新UI的唯一方法就是创建一个全新的元素。
React 更新策略
React DOM只会进行必要的更新来使DOM达到新的状态。
这种策略只考虑UI在一个指定时间的状态,而不考虑变化的过程,可以减少bug。
组件&props
组件即为代码复用的片段,类似于函数。
组件形式
组件有两种形式:函数和class
//函数组件
function Welcome(props){
return <h1>hello,{props.name}</h1>
}
//ES6-class
class Welcome extends React.Component{
render(){
return(
<div>
hello,{this.props.name}
</div>
);
}
}
渲染组件
React元素可以是DOM标签、或者是用户自己定义的组件
当用户自定义组件,它会将JSX接收道德属性以及子组件转换为单个对象传递给组件,即props
看如下例子:
class Welcome extends React.Component{
render(){
return(
<div>
hello,{this.props.name}
</div>
);
}
}
const elemen=<Welcome name="柳刀" />
ReactDOM.render(
element,
document.getElementById('root')
);
页面会渲染出:hello,柳刀
注意:自定义标签的名称必须为大写,React将会把小写字母开头的组件视为原生DOM组件。
组件的组合与提取
组件可以进行嵌套组合
通常每个新的React应用程序的顶层组件都应该是App组件
对于一些使用程度叫较高的组件,应该进行额外的定义以使组件更容易维护。
props只读
所有的React组件都必须保护它们的props不被更改
state&生命周期
state与props相似,但是可以完全受控与当前的组件。
组件第一次被渲染到DOM中时,被称为“挂载”(mount)
DOM中组件被删除的时候,应该清除计时器,被称为“卸载”(unmount)
见如下的时钟的例子:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
//生命周期方法
//在组件已经被渲染到DOM中后运行
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
//在组件即将被卸载时调用
componentWillUnmount() {
clearInterval(this.timerID);
}
//计时器函数,通过这个函数更新state
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')
);
state
state只能在组件初始化时进行赋默认值
state={}
或者在构造函数中进行构造;
state不能在除了以上两个地方之外使用 this.state.xxx 进行更新
只能使用
this.setState({xxx:xxx}) 进行更新
state的更新可能是异步的
React可能会将多个setState()合并为一个调用,而且props,state也可能异步更新,所以不应该依赖它们的值去决定下一个状态。
如:
//wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
为了解决该问题,可以使用:
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
使用参数对state进行更新
state的更新会被合并
为了提高性能,React将setState()合并调用,这里的合并是浅合并,简而言之,就和单独调用他们的效果是一样的。
数据是向下流动的
这被称为"自上而下"或者是"单向"数据流,任何的state总是所属于特定的组件,而从state派生的任何数据只能影响树中低于它们的组件。
这类似与一个瀑布,上层组件在瀑布上层,下层组件在瀑布下层,props是水流,state就是各个组件的水源。
React的数据流动是单向的,也是单层的,即数据不能跨组件流动,但是数据流能够跨组件流动。
事件处理
React事件的命名采用小驼峰而不是纯小写
使用JSX时需要传入一个函数作为事件处理函数而不是一个字符串
//传统
<button onclick="activateLasers()">
Activate Lasers
</button>
//React
<button onClick={activateLasers}>
Activate Lasers
</button>
React中不能通过返回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>
);
}
使用class定义组件,事件处理函数通常为一个方法
处理this
class不会默认绑定this,需要手动进行绑定
可以进行:
• 构造函数中绑定this
this.handle=this.handle.bind(this)
• 在调用时绑定this
onClick={this.handle.bind(this)}
• 在声明时使用箭头函数:class fields语法
handle=()=>{}
• 在回调中使用箭头函数
onClick={() => this.handleClick()}
这个方法的问题是在每次渲染组件的时候都会创建不同的回调函数,作为props传入子组件时,这些子组件可能会进行额外的重新渲染,所以不推荐使用。
向事件处理程序传递参数
有两种方法:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
条件渲染
元素变量
即通过某个state中的状态进行渲染元素的判断,使用这种方法可以增加代码重用。
&&
例子如下:
<div>
<h1>Hello!</h1>
{unreadMessages.length > 0 &&
<h2>
You have {unreadMessages.length} unread messages.
</h2>
}
</div>
以上例子能够实现是因为:
在JS中,true&&experssion总是返回experssion
false&&experssion总是返回false
三目运算
这里要注意的是,要在可读性和简便性上做好平衡,保持代码的可读性。
如果代码太复杂,应该提取组件。
阻止组件渲染
不希望渲染某组件时,可以直接render null
以下的例子中将不会进行渲染,用这种方法可以在必要的时候阻止特定组件的渲染。
class ClassName extends React.Component{
render(){
return null;
}
}
组件render返回null并不会影响组件的生命周期。
列表&Key
渲染多个组件
在React中渲染多个组件一般使用map方法,从已有数组中产生新数组。
下面的例子进行了解释:
const numbers=[1,2,3,4,5]
const listItems=numbers.map((number)=><li>{number}</li>);
ReactDOM.render(
<ul>{listItems}</ul>,
document.getElementById('root')
);
基础列表组件
规范化,在一个组件中渲染列表:
class NumberList(props){
const numbers=props.numbers;
const listItems = numbers.map((number) =>
<li key={number.toString()}>{number}</li>
);
return (
<ul>{listItems}</ul>
)
}
这个例子的作用与前面的例子相同,只不过为每个
key
key的作用是帮助React识别哪些元素发生了改变,所以需要给数组中的每个元素赋予一个特定的值。
key应该是元素在列表中拥有的独一无二的字符串,通常使用数据中的id作为元素的key。
如果传入的数据没用id,可以使用元素索引index作为key
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
但是如果列表项目的顺序可能发生变化,那不建议使用索引作为key值,这样会使性能变差,还会引起组件状态问题,如果不显示指定key值,React将默认使用索引作为key值。
用key提取组件
元素的key应该放在就近数组的上下文中。
如例子:
function ListItem(props) {
// 正确!这里不需要指定 key:
return <li>{props.value}</li>;
}
function NumberList(props) {
const numbers = props.numbers;
const listItems = numbers.map((number) =>
// 正确!key 应该在数组的上下文中被指定
<ListItem key={number.toString()} value={number} />
);
return (
<ul>
{listItems}
</ul>
);
}
在上面的例子中,应该在下文,即ListItem处引入数组key,因为它才是数组中的“直接”元素。
在大多数情况下,在map()方法中的元素设置key属性总是正确的。
key只是在兄弟节点之间必须唯一
数组元素只需要在它所在的元素中唯一即可,不需要全局唯一,在生成两个数组的时候,可以使用相同的key值。
key只会传递消息给React,而不会传递给所在的组件。需要在组件中使用key值时,应该使用其他的属性名称显式传递值。
在JSX中嵌入map()
可以将遍历返回的结果通过map()写在{}中,因为JSX允许在大括号嵌入任何表达式。
如果一个 map() 嵌套了太多层级,那可能就是你提取组件的一个好时机
表单
受控组件
受控组件即为使React成为state的唯一数据源。渲染表单的React组件还控制着用户输入过程中表单发生的操作。
如下例:
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('提交的名字: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<label>
名字:
<input type="text" value={this.state.value} onChange={this.handleChange} />
</label>
<input type="submit" value="提交" />
</form>
);
}
}
在这个例子中,名字表单的值始终为this.state.value,React为state唯一的数据源,handleChange将会在每次案件时执行并更新state。
对于受控组件,每个state突变都有一个相关的处理函数,这使得修改或者验证用户输入变得更容易。例如可以强制将用户输入大写字母。
handleChange(event) {
this.setState({value: event.target.value.toUpperCase()});
}
textarea标签
在html中,textarea通过子元素定义文本。
在react中,使用value属性代替,使得使用<textarea>
的表单和使用单行input表单非常类似。
select标签
在react中,select使用value属性
见例子:
class FlavorForm extends React.Component {
constructor(props) {
super(props);
this.state = {value: 'coconut'};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({value: event.target.value});
}
handleSubmit(event) {
alert('你喜欢的风味是: ' + this.state.value);
event.preventDefault();
}
render() {
return (
<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>
);
}
}
以上三个组件都接收一个value属性,可以使用这个属性来实现受控组件。
可以将数组传入到value属性中,以支持select标签中的多个选项。
文件input
在html中,
<input type="file" />
的value为只读,所以是一个非受控组件
处理多个输入
处理多个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>
);
}
}
值得注意的是,使用以下语句进行state的更新
this.setState({
[name]: value
});
这是ES6计算属性名称的语法。
受控输入空值
在受控组件上指定value的prop会阻止用户更改输入。如果指定value,输入仍可编辑,可能是意外将value设置为undefined&null
非受控组件
状态提升
多个组件需要反映相同的变化数据,这时建议将共享状态提升到最近的共同父组件中去。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
this.props.onTemperatureChange(e.target.value);
}
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
关于状态提升的总结
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
虽然提升 state 方式比双向绑定方式需要编写更多的“样板”代码,但带来的好处是,排查和隔离 bug 所需的工作量将会变少。由于“存在”于组件中的任何 state,仅有组件自己能够修改它,因此 bug 的排查范围被大大缩减了。此外,你也可以使用自定义逻辑来拒绝或转换用户的输入。
如果某些数据可以由 props 或 state 推导得出,那么它就不应该存在于 state 中。举个例子,本例中我们没有将 celsiusValue 和 fahrenheitValue 一起保存,而是仅保存了最后修改的 temperature 和它的 scale。这是因为另一个输入框的温度值始终可以通过这两个值以及组件的 render() 方法获得。这使得我们能够清除输入框内容,亦或是,在不损失用户操作的输入框内数值精度的前提下对另一个输入框内的转换数值做四舍五入的操作。
组合VS继承
React推荐使用组合而不是继承来实现组件间的代码重用
包含关系
有些组件在使用前无法得知其子组件的内容,这样的情况可以见例子如下:
class Part1 extends React.Componment{
render(){
return(
<div>
{props.children}
</div>
);
}
}
class Part2 extends React.Componment{
render(){
return(
<div>
子组件内容
</div>
);
}
}
该结构即在父组件中为子组件留出位置,该位置可以用例子中的 {props.children} 表示,也可以使用
props.left 或者其他的表达式表示。
特例关系
存在某个组件是一个组件的特例的情况,该情况也可以使用组合进行标识
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
在以上例子中,SignUpDialog是Dialog的特例,只要把SignUpDialog作为Dialog的父组件,将数据流向Dialog即可。
继承
FaceBook那么多页面都没用到继承,React的组合这么好用了,别用什么继承了。
注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
如果需要在组件间复用非UI功能,建议提取为单独的模块,使用import引入。
[React]核心概念的更多相关文章
- [WIP]React 核心概念
创建: 2019/05/01 Hello World ReactDOM.render( <p>sample</p>, document.getElementById('ro ...
- react 教程—核心概念
react 核心概念 : https://react.docschina.org/docs/getting-started.html(官网) 或 https://www.w3cschool.cn/ ...
- React 核心思想之声明式渲染
React 发展很快,概念也多,本文目的在于帮助初学者理清 React 核心概念. React 及 React 生态 React 的核心概念只有 2 点: 声明式渲染(Declarative) 基于组 ...
- Redux 核心概念
http://gaearon.github.io/redux/index.html ,文档在 http://rackt.github.io/redux/index.html .本文不是官方文档的翻译. ...
- fusionjs 学习二 核心概念
核心概念 middleware 类似express 的中间件模型(实际上是构建在koa中间件模型上的),但是和koa 的中间件有差异 fusionjs 的中间件同时可以运行在浏览器页面加载的时候 se ...
- 深入理解Vue组件3大核心概念
摘要: 搞懂Vue组件! 作者:浪里行舟 原文:详解vue组件三大核心概念 Fundebug经授权转载,版权归原作者所有. 前言 本文主要介绍属性.事件和插槽这三个vue基础概念.使用方法及其容易被忽 ...
- 领域驱动设计(DDD)部分核心概念的个人理解
领域驱动设计(DDD)是一种基于模型驱动的软件设计方式.它以领域为核心,分析领域中的问题,通过建立一个领域模型来有效的解决领域中的核心的复杂问题.Eric Ivans为领域驱动设计提出了大量的最佳实践 ...
- Javascript本质第一篇:核心概念
很多人在使用Javascript之前都至少使用过C++.C#或Java,面向对象的编程思想已经根深蒂固,恰好Javascript在语法上借鉴了Java,虽然方便了Javascript的入门,但要深入理 ...
- [程序设计语言]-[核心概念]-02:名字、作用域和约束(Bindings)
本系列导航 本系列其他文章目录请戳这里. 1.名字.约束时间(Binding Time) 在本篇博文开始前先介绍两个约定:第一个是“对象”,除非在介绍面向对象语言时,本系列中出现的对象均是指任何可以有 ...
随机推荐
- python Could not find a version that satisfies the requirement pymysql (from versions: none) ERROR: No matching distribution found for pymysql
使用pip安装pymysql出错;Could not find a version that satisfies the requirement cryptography (from pymysql) ...
- MySQL的万字总结(缓存,索引,Explain,事务,redo日志等)
hello,小伙伴们,好久不见,MySQL系列停更了差不多两个月了,也有小伙伴问我为啥不更了呢?其实我去看了MySQL的全集,准备憋个大招,更新篇长文(我不会告诉你是因为我懒的). 好了,话不多说,直 ...
- 【BIM】BIMFACE中创建矢量文本
背景 在三维模型产品的设计中,针对空间的管理存在这样一个普遍的需求,那就是在三维模型中,将模型所代表的空间通过附加文本的方式来展示其所代表的实际位置或功能,之前尝试过若干方式,比如直接在建模的时候,将 ...
- 初识JVM:(一)JVM工作原理和流程
本文主要参考:http://blog.csdn.net/CSDN_980979768/article/details/47281037?locationNum=7&fps=1 声明:主要用于个 ...
- 将xml处理为json对象数组
function xmlStr2js(xmlStr) { var tagNames = xmlStr.match(/<\w+>/g) tagNames = deWeightTagNames ...
- (转)协议森林05 我尽力 (IP协议详解)
协议森林05 我尽力 (IP协议详解) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! IPv4与IPv6头部的对比 我们已经在I ...
- 如何查看子线程中的GC Alloc
1)如何查看子线程中的GC Alloc2)Build时,提示安卓NDK异常3)如何获得ParticleSystem产生的三角形数量4)关于图片通道的问题5)GPUSkinning导致模型动画不平滑 M ...
- 【股票盯盘软件】01_程序员炒股之开发一款极简风格的股票盯盘软件StockDog_V1.0.0.1
1.前言 话说最近一段时间受疫情的影响,股市各种妖魔横行.本人作为一个入股市不满三年的小韭菜,就有幸见证了好几次历史,也是满心惊喜,就权当是接受资本市场的再教育了吧. 小韭菜的炒股方法其实很简单,这两 ...
- 网络安全从入门到精通(第一章-1)Web服务器通信原理
本文内容 IP地址 域名 端口 HTTP协议 从访客角度看网页浏览器流程 常见服务器系统 路径 Web容器 常见的Web容器 !!!多动手,多动手,只看只听是不行的!!! 1,IP地址:就是计算机在互 ...
- Head First设计模式——原型模式和访问者模式
原型 原型模式:当创建给定类的过程很昂贵或很复杂时,就使用原型模式. 我们在进行游戏的时候游戏会动态创建怪,而怪时根据场景的不同而变化创建的,英雄自己也会创建一些随从.创建各式各样的怪兽实例,已经越来 ...