本文共 18642 字,大约阅读时间需要 62 分钟。
React语法速查
JSX介绍
JSX语法中,可以在大括号内放置任何有效的JavaScript表达式。例如2+2
、user.firstName
或formatName(user)
等均是有效的JavaScript表达式。
JSX可以通过使用引号,来将属性指定为字符串字面量,也可以使用大括号来在属性值中插入一个JavaScript表达式。
class JSXShow extends React.Component{ constructor( props ){ super(props); this.state = { user: { firstName: 'n', lastName: 'tx' } } } formatName( user ){ return user.firstName + '·' + user.lastName; } render(){ return() }}my name is { this.formatName( this.state.user )}; ;
元素渲染
要将一个元素渲染为DOM,可以定义一个根节点<div id="root"></div>
,该节点内所有内容都由React DOM管理。
想要将一个 React 元素渲染到根 DOM 节点中,只需把它们一起传入 ReactDOM.render()
:
const element =Hello, world
;ReactDOM.render( element, document.getElementById('root'));
React 元素是不可变对象。一旦被创建,你就无法更改它的子元素或者属性。一个元素就像电影的单帧:它代表了某个特定时刻的 UI。
想要更新已渲染的元素,最简单的方式是创建一个全新的元素,并将其传入ReactDOM.render()
。但在实践中,大多数 React 应用只会调用一次 ReactDOM.render()。所以就需要将相应代码封装进有状态组件中去。
React DOM 会将元素和它的子元素与它们之前的状态进行比较,并只会进行必要的更新来使 DOM 达到预期的状态。
组件
组件是将UI拆分为独立可复用的代码片段,并对每个片段进行独立构思。
从概念上,组件类似于JavaScript函数,它接受任意的传参(即props),并返回用于描述页面展示内容的React元素。
所以定义组件最简单的方式是编写JavaScript函数,以下函数就是一个有效的React组件,它接收唯一带有数据的props参数,并返回一个React元素。这称为函数组件。
function Welcome( props ){ returnHello, { props.name}
}
也可以使用ES6的class来定义组件。
class Welcome extends React.Component { constructor( props ){ super(props) } render() { returnHello, { this.props.name}
; }}
无论是函数声明组件还是class声明组件,都决不能修改自身的props。所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。
当然,应用程序的 UI 是动态的,并会伴随着时间的推移而变化。为满足动态变化需求,另有一种称之为 “state”。在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。
State
state类似于props,但state是当前class组件内部私有的,并且完全受控于当前class组件。
以下引用官方文档示例:
class Clock extends React.Component{ constructor( props ){ super( props ); this.state = { date: new Date() }; } // 生命周期方法: componentDidMount() 方法会在组件已经被渲染到 DOM 中后运行 componentDidMount(){ this.timerID = setInterval( ()=>{ this.tick(); }, 1000 ) } // 生命周期方法: componentWillUnmount() 方法会当组件被销毁时释放所占用的资源 componentWillUnmount(){ clearInterval( this.timerID ); } tick(){ this.setState( ( state, props )=>{ console.log( state ); console.log( props ); return { date: new Date() } } ) } render(){ return() }}Hello World!
It is { this.state.date.toLocaleTimeString()}
代码逻辑如下:
-
当
<Clock />
被传给ReactDOM.render()
的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化this.state
。我们会在之后更新 state。 -
之后 React 会调用组件的
render()
方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。 -
当 Clock 的输出被插入到 DOM 中后,React 就会调用
ComponentDidMount()
生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的tick()
方法。 -
浏览器每秒都会调用一次
tick()
方法。 在这方法之中,Clock 组件会通过调用setState()
来计划进行一次 UI 更新。得益于setState()
的调用,React 能够知道 state 已经改变了,然后会重新调用render()
方法来确定页面上该显示什么。这一次,render()
方法中的this.state.date
就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。 -
一旦 Clock 组件从 DOM 中被移除,React 就会调用
componentWillUnmount()
生命周期方法,这样计时器就停止了。
class组件中正确使用state应了解以下3件事:
-
不要直接修改state,如
this.state.comment = 'Hello';
,而应使用setState()
:this.setState({comment: 'Hello'});
。构造函数是唯一可以给 this.state 赋值的地方。 -
State 的更新可能是异步的,出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。因为 this.props 和 this.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。
例如,此代码可能会无法更新计数器:// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
要解决这个问题,可以让 setState() 接收一个函数而不是一个对象。这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
// Correct this.setState((state, props) => ({ counter: state.counter + props.increment }));
-
State 的更新会被合并。当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state。
数据是向下流动的,组件可以选择把它的state作为props向下传递到它的子组件中:<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
,这点也适用于自定义组件:<FormattedDate date={this.state.date} />
事件处理
React元素的事件处理类似于DOM元素,区别在语法不同:React事件的命名采用小驼峰,而非DOM元素的纯小写;使用JSX语法时需要传入一个函数作为事件处理函数,而非字符串。
// 传统HTML// React
react事件中不能通过返回false的方式阻止默认行为,你必须显示的使用preventDefault()。另外函数中的参数e
是一个合成事件。
handleClick( e ){ e.preventDefault(); console.log( '.....123123' );}render(){ return ( )}
当使用class组件时,通常会将事件处理函数声明为class中的方法,以下为示例:
class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true}; // 为了在回调函数中使用 `this`,这个绑定是必不可少的 this.handleClick = this.handleClick.bind(this); } handleClick() { this.setState(state => ({ isToggleOn: !state.isToggleOn })); } render() { return ( ); }}
由于在class组件中,方法默认不会绑定this,所以需要如上例所示在构造器函数constructor中显式为事件处理函数绑定this。除了这个方法外,还有两种方式也可以解决this绑定问题。
方法一是使用 class fields 正确的绑定回调函数:
class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true}; } handleClick =( e )=>{ console.log( e ); this.setState( ( state )=>{ return { isToggleOn: !state.isToggleOn } } ) } render() { return ( ); }}
方法二是在回调中使用箭头函数:
class Toggle extends React.Component { constructor(props) { super(props); this.state = { isToggleOn: true}; } handleClick =( e )=>{ console.log( e ); this.setState( ( state )=>{ return { isToggleOn: !state.isToggleOn } } ) } render() { return ( // 此语法确保 `handleClick` 内的 `this` 已被绑定。 ); }}
方法二的问题在于每次渲染 Toggle组件时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。
所以官方的推荐是在构造器constructor中使用bind
绑定this 或者 使用方法一class fileds语法来避免方法二造成的性能问题。
在事件处理函数实践中,向事件处理函数传递参数应用场景很多。比如循环时通常会向事件处理函数传递额外的参数。以下示例中id是要删除的那一行的ID,有两种方式都可以向事件处理函数传递参数:
class TepCom1 extends React.Component { constructor(props) { super(props); this.state = { }; } deleteRow( id, a, b, c, d ){ console.log( id ); console.log( a ); console.log( b ); console.log( c ); console.log( d ); } render() { return ( // 通过箭头函数的方式: react的事件对象e必须显示的进行传递,事件对象e显式传递的位置由开发者决定,这里我定在参数列表的末位,也可以根据实际需要放在参数列表的任意位置 ); }}// 以下为点击触发事件处理函数打印结果,可以看到当事件对象e显式传递时,才会被传递给事件处理函数参数d。如事件对象e没有显式传递,console.log( d )的打印结果将会为undefined/*console.log( id ): BBconsole.log( a ): ccconsole.log( b ): GGQconsole.log( c ): dfconsole.log( d ): Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: button, …}*/class TepCom2 extends React.Component { constructor(props) { super(props); this.state = { }; } deleteRow( id, a, b, c, d ){ console.log( id ); console.log( a ); console.log( b ); console.log( c ); console.log( d ); } render() { return ( // 通过 bind 的方式: react的事件对象e会被隐式的进行传递,且它的位置永远处于参数列表的末位 ); }}// 以下为点击触发事件处理函数打印结果,可以看到尽管事件对象e尽管没有显式传递,但仍被传递给事件处理函数参数d/*console.log( id ): BBconsole.log( a ): ccconsole.log( b ): GGQconsole.log( c ): dfconsole.log( d ): Class {dispatchConfig: {…}, _targetInst: FiberNode, nativeEvent: MouseEvent, type: "click", target: button, …}*/
以上两种方式等价,分别是通过箭头函数和Function.prototype.bind
来实现。
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
这里补充下React事件对象e的一个知识点,如要想从React事件对象中访问系统属性value时,可以通过e.target.value
,如想从React事件对象中访问自定义属性时,可以通过e.target.dataset
。
条件渲染
在React中,可以创建不同的组件来封装各种需要的行为。然后依据应用的不同状态,可以只渲染对应状态下的部分内容。
// 1.创建不同组件来封装不同行为function UserGreeting( props ){ return (Welcome back!
)}function GuestGreetin( props ){ return (Please sing up.
)}// 登陆登出时显示的问候文案组件class Greeting extends React.Component{ constructor( props ){ super( props ); this.state = { } } render(){ if( this.props.isLoginInProp ){ return}else { return } }}// 登陆状态控制组件class LoginControl extends React.Component{ constructor( props ){ super(props); this.state = { isLoginIn: false } this.handleLoginClick = this.handleLoginClick.bind( this ) this.handleLogOutClick = this.handleLogOutClick.bind( this ) } handleLoginClick(e){ console.log( e ); this.setState( { isLoginIn: true } ) } handleLogOutClick(){ this.setState( { isLoginIn: false } ) } render(){ // 2.依据应用的不同状态,可以只渲染对应状态下的部分内容 let isLoggedIn = this.state.isLoginIn; let buttonCom; if( isLoggedIn ){ buttonCom = }else { buttonCom = } return ( { /* 子组件Greeting根据是否登陆来显示问候文案 */}) }}{ buttonCom}
阻止组件渲染的方法是让 render
方法直接返回 null
。示例如下:
// 根据不同状态返回内容function WarnBanner( props ){ if( !props.warn ){ return null; } return (Warning!)}class Page extends React.Component{ constructor( props ){ super( props ); this.state = { showWarn: true }; } handleShowWarn(e){ this.setState( { showWarn: !this.state.showWarn } ) } render(){ return () }}
列表
react中列表的渲染有如下示例,同时需添加key属性,key能帮助React识别哪些元素改变,通常建议取值为该元素在列表中的独一无二的字符串,一般使用id来作为元素的key,而当元素确定没有id时,万不得已也可使用元素索引index作为key的值,但如果列表项目顺序未来可能会发生变化时,则不建议使用索引来作为key值,因为这会导致性能变差,还可能引起组件状态问题。如果不指定显式key值,React会默认使用索引作为列表项目的key值。
function ListItem( props ){ return (
- { listNumbers}
React表单
HTML表单元素通常自己维护state(状态),并根据用户输入进行更新。而在React中,可变状态通常保存在组件的state属性中,并且只能通过setState()
来更新。
所以React表单组件可以结合以上两点,既可以使react表单组件的state成为唯一数据源,还可以控制用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”:
class NameForm extends React.Component{ constructor( props ){ super( props ); this.state = { value: '' } } handleChange( e ){ this.setState( { value: e.target.value } ) } handleSubmit( e ){ console.log( `提交的名称是: ${ this.state.value}` ); e.preventDefault(); } render(){ return () }}
由于在表单元素上设置了 value 属性,因此显示的值将始终为 this.state.value,这使得 React 的 state 成为唯一数据源。由于 handlechange 在每次按键时都会执行并更新 React 的 state,因此显示的值将随着用户输入而更新。
对于受控组件来说,每个 state 突变都有一个相关的处理函数。这使得修改或验证用户输入变得简单。例如,如果我们要强制要求所有名称都用大写字母书写,我们可以将 handlechange 改写为:
handleChange(e) { this.setState({ value: e.target.value.toUpperCase()});}
不同于HTML中<textarea>
元素通过其子元素定义其文本,React中<textarea>
使用value
属性代替。如此就使得<textarea>
类似于单行input
元素。具体参照上例。
HTML中select
创建下拉列表标签时,会在option
中根据selected
属性来表示该项已被选中。但在React中,不使用selected
属性,而是根 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 (); }}
如要对select
标签实行多选时,可以将数组传入根select标签的value属性中:<select multiple={true} value={['B', 'C']}>
。
以上3个表单标签:<input type="text">
、<textarea>
和<select>
都接收一个value
属性,可以以此来实现受控组件。
当需要处理多个 input 元素时,我们可以给每个元素添加 name 属性,并让处理函数根据 event.target.name 的值选择要执行的操作:
class CkAndInput extends React.Component{ constructor( props ){ super(props); this.state = { inputCk: true, inputName: 2 } } handleChange( e ){ let target = e.target; let value = target.type === 'checkbox' ? target.checked : target.value; let name = target.name; // 由于 setState() 自动将部分 state 合并到当前 state, 只需调用它更改部分 state 即可 this.setState( { [name]: value } ) } render(){ return () }}
状态提升
在React中,当多个组件都需要反映相同的变化数据时,可以将这个共享的变化数据提升到最近的父组件中去。
如下示例首先创建一个计算水温是否沸腾的温度计算器:
// 判断水温是否沸腾并渲染DOMfunction BoilingVerdict( props ){ if( props.celsius >= 100 ){ return) }}The water would be boil.
}else { returnThe water would not boil.
}}// 输入摄氏水温值,并将该温度作为props参数传递给BoilingVerdict组件进行渲染,给出是否已沸腾的结果class Calculator extends React.Component{ constructor( props ){ super( props ); this.state = { temperature: '' } } handleChange( e ){ this.setState({ temperature: e.target.value }) } render(){ return (
以此基础,创建一个状态提升的实践组件:
const scaleNames = { c: 'Celsius', f: 'Fahrenheit'}; function toCelsius(fahrenheit) { return (fahrenheit - 32) * 5 / 9;}function toFahrenheit(celsius) { return (celsius * 9 / 5) + 32;}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 BoilingVerdict(props) { if (props.celsius >= 100) { return); }} 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}); } 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 (The water would boil.
; } returnThe water would not boil.
;}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 (
梳理逻辑:
- React 会调用 DOM 中
<input>
的onChange
方法。在本实例中,它是TemperatureInput
组件的handleChange
方法。 TemperatureInput
组件中的handleChange
方法会调用this.props.onTemperatureChange()
,并传入新输入的值作为参数。其 props 诸如onTemperatureChange
之类,均由父组件Calculator
提供。- 起初渲染时,用于摄氏度输入的子组件
TemperatureInput
中onTemperatureChange
方法为Calculator
组件中的handleCelsiusChange
方法,而,用于华氏度输入的子组件TemperatureInput
中的onTemperatureChange
方法为Calculator
组件中的handleFahrenheitChange
方法。因此,无论哪个输入框被编辑都会调用Calculator
组件中对应的方法。 - 在这些方法内部,
Calculator
组件通过使用新的输入值与当前输入框对应的温度计量单位来调用this.setState()
进而请求 React 重新渲染自己本身。 - React 调用
Calculator
组件的render
方法得到组件的 UI 呈现。温度转换在这时进行,两个输入框中的数值通过当前输入温度和其计量单位来重新计算获得。 - React 使用
Calculator
组件提供的新props
分别调用两个TemperatureInput
子组件的render
方法来获取子组件的 UI 呈现。 - React 调用
BoilingVerdict
组件的render
方法,并将摄氏温度值以组件props
方式传入。 - React DOM 根据输入值匹配水是否沸腾,并将结果更新至 DOM。我们刚刚编辑的输入框接收其当前值,另一个输入框内容更新为转换后的温度值。
在 React 应用中,任何可变数据应当只有一个相对应的唯一“数据源”。通常,state 都是首先添加到需要渲染数据的组件中去。然后,如果其他组件也需要这个 state,那么你可以将它提升至这些组件的最近共同父组件中。你应当依靠自上而下的数据流,而不是尝试在不同组件间同步 state。
组合
React 有十分强大的组合模式。官方推荐使用组合而非继承来实现代码的重用。
包含关系
有些组件无法提前知晓它们子组件的具体内容。在 Sidebar(侧边栏)和 Dialog(对话框)等展现通用容器(box)的组件中特别容易遇到这种情况。建议这些组件使用一个特殊的 children prop 来将他们的子组件传递到渲染结果中:
class FancyBorder extends React.Component{ constructor( props ){ super( props ); this.state = { }; } render(){ return ({ this.props.children}) }}class WelcomeDialog extends React.Component{ constructor( props ){ super( props ); this.state = { }; } render(){ return () }} Welcome
Thank you for visiting our spacecraft!
特例关系
有些时候,我们会把一些组件看作是其他组件的特殊实例,比如 WelcomeDialog 可以说是 Dialog 的特殊实例。在 React 中,我们也可以通过组合来实现这一点。“特殊”组件可以通过 props 定制并渲染“一般”组件:
function Dialog(props) { return ();}function WelcomeDialog() { return ( );} { props.title}
{
props.message}
喜欢本文请扫下方二维码,关注微信公众号: 前端小二,查看更多我写的文章哦,多谢支持。
转载地址:https://blog.csdn.net/qq_34832846/article/details/104915688 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!