React作为当前全球流行的前端框架,是许多前端小伙伴在工作和学习中很难绕开的技术栈,学习React不仅是多掌握一个前端技能,同时由于React写法偏向原生的js语法,熟练使用React可以提升我们对于Javascript语法的掌握。
以下是笔者整理的jsx基础语法,文章内容较长,但描述方式和使用的代码示例相对浅显,易于阅读,欢迎您的阅读,阅读过程中如有发现错漏或疑惑,欢迎在评论区交流探讨!
一、jsx基础语法
1、jsx基础语法规则
(1)定义虚拟dom时,不要使用引号;
(2)标签中混入js表达式时要使用花括号{};
(3)样式类名指定不要使用class,使用className;
(4)内联样式,要使用style = {{ key: value }}的形式去写;
// 注意写在对象中要使用驼峰形式
{{ font-size: '16px' }} // bad
{{ fontSize: '16px' }} // good
(5)只能有一个根标签;
(6)标签必须闭合;
const vDom = <input value="text"> // bad
const vDom = <input value="text" /> // good
ReactDOM.render(vDom, document.getElementById('test');
(7)标签首字母
若首字母小写,则将标签转为对应的html中的同名元素,若html中无该标签对应的同名元素,则报错;
若首字母大写,则当成自定义组件(类似vue的子组件),react去渲染对应的组件,若组件没有定义,则报错;
(8){}的混合表达式为数组映射操作时,react会帮我们进行循环渲染;
const arr = ['a', 'b', 'c'];
const vDom = (
<div>
<h1>js列表测试</h1>
<ul>{arr}</ul> // 会得到渲染结果:abc(需要添加其他效果,如<li>a</li>,需要对数组进行遍历映射操作)
</div>
)
const vDom = (
<div>
<h1>js列表测试</h1>
<ul>
{
arr.map((item, index) => {
return <li key={index}>{item}</li> // 遍历映射操作
})
}
</ul>
</div>
)
2、jsx表达式
(1)jsx的{}中可以使用js表达式,但是不能使用js语句!
(2)js表达式与js语句的区别如下:
A. 表达式:一个表达式会产生一个值,可以放在任何需要值的地方,例如:
(1). a // 变量
(2). a + b // 会产生值的运算表达式
(3). fun(1) // 调用函数(不管怎样函数都有默认return)
(4). arr.map() // 调用属性挂载的函数
(5). function fun() {} // 定义函数(即使没有return,也会默认返回函数本身)
B. 语句(代码块):不产生值,用于控制代码走向的,例如:
(1). if() {}
(2). for() {}
(3). switch() {case:xxxx}
(3)使用对比示例
const arr = ['a', 'b', 'c'];
// bad for语句没有输出,{}无法对结果进行语法转义
const vDom = (
<div>
<h1>js列表测试</h1>
<ul>
{
for (let i = 0, len = arr.length; i < len; i ++) {
<li>{arr[i]}</li> // bad,在{}中不能直接写js语句
}
}
</ul>
</div>
)
// good 有输出,{}可以对结果进行语法转义
const vDom = (
<div>
<h1>js列表测试</h1>
<ul>
{
arr.map((item, index) => {
// 跟vue类似,key是diff算法的比较标识,使用for循环渲染时不加上会报错!
return <li key={index}>{item}</li> // good
})
}
</ul>
</div>
)
3、类式组件
(1)react内置了一个类React.Component,写类式组件需要继承这个React.Component,没有特殊情况,可以直接继承它的构造器,不用另外写。
class MyComponent extends React.Component {
// 重写render函数,用于定义组件内容
render() {
return <h2>类式组件</h2>
}
}
ReactDOM.render(<MyComponent/>, document.getElementById('test');
(2)类式组件的渲染过程
ReactDOM.render(<MyComponent/>, document.getElementById('test');
/**
* 1、react解析组件标签,找到了MyComponent组件
* 2、发现组件是类定义的,随后new出该类的实例,并通过实例对象调用到原型上的render方法;
* 3、将render返回的虚拟DOM转化为真实DOM,随后呈现在页面中;
*/
(3)组件实例对象
(1)state即状态,用来存储组件数据的,复杂组件才有state,简单组件(函数式组件)没有state
二、React类式组件三大基本属性
React类式的三大基本属性包括:state、props和refs,学会使用这三大属性的,就可以满足对类式组件的简单应用需求了。三大属性的基本作用如下:
(1)state —— 状态机,主要用于保存组件状态(存储需要实现响应式的数据)
(2)props —— 用于组件之间传递数据,组件内部props时只读状态的
(3)refs —— 该语法的能力与vue2中的refs基本一致,在标签上使用ref标识标签,React会在refs中统一收集全部被ref标识的标签 。
三、State详解
1、简介
state即状态,用来存储组件数据的,复杂组件(类式组件)才有state,简单组件(函数式组件)没有state。
class Weather extends React.Component{
constructor(props){
super(props);
this.state = { name: 'testname' }
}
render(){
// this指向实例对象,state就是实例对象生成的,通过this.state即可获取到!
return <h1>My name is {this.state.name}</h1>
}
}
ReactDOM.render(<Weather/>, document.getElementById('test'));
2、state的简写方式
(1)类中的属性可以放在constructor之外定义
class Weather extends React.Component {
constructor(props) {
super(props);
this.state = {isHot: false, wind: '微风'}
}
// 等同于:
state = {isHot: false, wind: '微风'}
}
(2)并且,由于JS中箭头函数的指向是箭头函数定义位置的外层this指向,因此,在react中,类式组件中定义的箭头函数可以直接使用如下方式调用state:
建议:在react中,所有定义的函数都写成赋值语句形式 + 箭头函数(可以直接改变this指向)!
class Weather extends React.Component {
constructor(props) {
super(props);
}
state = {isHot: false, wind: '微风'}
changeWeather = () => {
const isHot = this.state.isHot;
// 此时的this会指向Weather,而不用在constructor中写this.changeWeather = this.changeWeather.bind(this)
this.setState({isHot: isHot});
}
}
3、state总结
(1)state是组件最重要的属性,值是对象(key-value形式);
(2)组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(响应式);
(3)注意事项:
A. 组件中render的this为组件实例对象;
B. 组件自定义的方法的this为undefined,解决方式:
-
强制在constructor中使用bind()改变函数的this指向;
-
使用箭头函数;
C. 状态数据state不能直接修改,需要通过setState函数进行修改;
四、Props详解
1、简介
props是用来传递组件数据的,学过vue的小伙伴应该比较熟悉,react和vue中的props在用法以及作用上基本一致。
class Person extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
</ul>
)
}
}
// 调用Person组件并通过props传入数据
ReactDOM.render(<Person name="james"/>, document.getElementById('test'));
2、批量传递props
React提供了类似展开符...的语法糖,用于props的批量传递数据。在React的标签属性传递时,可以直接使用...展开对象(React会将其转化为一个解构赋值的语法)。
class Person extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
</ul>
)
}
}
let p = {name: "testName", age: 18}
// 调用Person,批量传入name和age
ReactDOM.render(<Person {...p}/>, document.getElementById('test'));
4、对Props进行数据类型和默认值限制
为保证渲染的数据类型可控,在实际应用中通常需要对传入的props,避免意外的数据类型造成系统错误。
(1)历史溯源:在react15之前,使用React.propsType设置限制内容,即:限制内容的对象是挂在React上的;在React15之后将这个抽离了出来,成为了一个单独的包props-types.js。
(2)对props进行类型限制 —— propsTypes
Person.propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
(3)对props指定默认标签属性值 —— defaultProps
Persion.defaultProps = {
sex: "未知",
age: 18
}
(4)综合使用示例
class Person extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
}
// 限制props内容
Person.propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
// 设置props部分属性标签的默认值
Persion.defaultProps = {
sex: "未知",
age: 18
}
// p.sex不存在,传进props时会默认为"未知"
let p = {name: "testName", age: 18, speak: () => {}}
ReactDOM.render(<Person {...p}/>, document.getElementById('test'));
5、Props简写
(1)以上的写法是将props的限制propTypes和默认值defaultProps写在了组件类之外,其实也可以直接写在组件类之中,这样可以使组件类包裹的逻辑更加清晰!
在组件内部,写一个对象挂在组件类上,需要借助static关键字,static声明的对象是直接挂在组件类上的(类似java的语法)
class Person extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
// 正确写法:使用static关键字,使其挂在组件类上
static propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
// 错误写法:这样是将propTypes定义在了组件实例上,而不是组件类上
propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
static defaultProps = {
sex: "未知",
age: 18
}
}
class Person extends React.Component {
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
// bad:这样是将propTypes定义在了组件实例上,而不是组件类上
propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
}
6、props和constructor的关系
props和constructor的关系:如果写了constructor并且在constructor中需要使用this.props时,需要写上super(props)把props传进来,否则在构造器constructor中会出现this.props未定义(undefined)的问题:
class Person extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // 不添加super(props),则此处的this.props为undefined
}
render() {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
}
7、函数式组件中的props
props虽然是类式组件的三大基本属性,但是在函数式组件中同样可以使用props进行组件数据传递。
// 函数式组件
function Person(props) {
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>年龄:{this.props.age}</li>
<li>性别:{this.props.sex}</li>
</ul>
)
}
// 直接将对于props的限制propTypes和默认值defaultProps挂在函数上即可
// 限制props内容
Person.propTypes = {
name: PropTypes.string.isRequired, // string表示name必须为String类型,isRequired表示必传
sex: PropTypes.string,
age: PropTypes.number,
speak: PropTypes.func // 限制speak类型为函数,由于function是一个关键字,因此React使用func表示function类型
}
// 设置props部分属性标签的默认值
Persion.defaultProps = {
sex: "未知",
age: 18
}
let p = {name: "testName", age: 18, speak: () => {}}
ReactDOM.render(<Person {...p}/>, document.getElementById('test'));
8、props总结
(1)每个组件对象都有props属性,组件标签的自定义属性都存在props中;
(2)作用:用于组件之间传递数据,组件内部props时只读状态的;
(3)组件内部读取props的方式:this.props.name;
(4)对props进行限制:
// react15.5之前,目前已弃用
Persion.propTypes = {
name: React.PropTypes.string; // 直接在React对象上拿限制对象PropTypes(会使React对象变得臃肿)
}
// react新版本的方式,引入props-types.js包,其中就包含对象PropTypes
Persion.propTypes = {
name: PropTypes.string;
}
(5)对props属性设置默认值:
Persion.defaultProps = {
name: 'testName'
}
(6)扩展属性:将对象的所用属性通过...的方式传递给props
const obj = { name: "testname", age: 18 }
ReactDOM.render(<Person {...obj}/>, document.getElementById('test'));
(7)在构造函数constructor中使用this.props时,记得使用super(props)把props取出来
class Person extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // 不添加super(props),则此处的this.props为undefined
}
}
五、Refs详解
1、简介
在标签上使用ref标识标签,React会在refs中统一收集全部被ref标识的标(该语法的能力与vue2中的refs基本一致 )
2、refs的三种使用形式
-
字符串形式的ref(不被官网方推荐,接近废弃);
-
回调函数形式的ref;
-
使用createRef API(目前react最推荐的写法);
3、refs使用方式一(不推荐):字符串形式
原因:在React展示的讨论区连接中表明,过度使用字符串类型的ref可能会存在一些效率问题!
使用示例如下:
class Person extends React.Component {
showData = () => {
const { value } = this.refs.inputId;
alert(value);
}
render() {
return (
<div style={{ display: 'flex', alignItems: 'center' }}>
<p>输入框:</p>
// 使用ref标记元素
<input ref="inputId" style={{height: '20px'}} />
<button onClick={this.showData} style={{marginLeft: '20px'}}>测试</button>
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'));
4、refs使用方式二:回调函数形式
回调函数形式的ref —— 回调函数的参数即当前的node节点,通过赋值将node节点放到组件实例上。React会自动调用ref上的回调函数!
class Person extends React.Component {
showData = () => {
// 点击展示输入框中的内容
const { input1 } = this;
alert(input1.value);
}
render() {
return (
<div>
// 使用回调函数的形式标记ref
<input ref={currentNode => this.input1 = currentNode} />
<button onClick={this.showData}>测试</button>
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'));
实现细节:回调函数形式的ref在更新的时候会被调用两次
原因:第一次回调函数的参数currentNode === null是为了有一个清空的初始化动作;即:React先赋一个null的值把原来标签的位置完全清空,再把一个新的标签内容写到那个位置!
// render更新的时候,该回调函数被调用两次,第一次的currentNode===null,第二次的currentNode才是当前节点
<input ref={currentNode => this.input1 = currentNode} />
解决方法:不要用内联的形式写回调函数,用class定义的形式来写ref的回调函数(尽管这种区别在大多数情况下无关紧要)
class Person extends React.Component {
showData = () => {
// 点击展示输入框中的内容
const { input1 } = this;
alert(input1.value);
}
getInput = (currentNode) => {
this.input1 = currentNode
}
render() {
return (
<div>
{/*内联函数形式,会存在两次调用的问题*/}
<input ref={currentNode => this.input1 = currentNode} />
{/*class调用形式,不会出现更新时两次调用的问题*/}
<input ref={this.getInput} />
<button onClick={this.showData}>测试</button>
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'));
5、refs使用方式三(推荐):使用createRef API
createRef —— 调用后会返回一个容器,该容器可以存储被ref所标识的节点(这种写法有点类似vue3的函数式定义)
class Person extends React.Component {
// 注意:createRef是“专人专用”的,一个createRef是同时放多个节点的话,后面的节点会覆盖前面的节点
myRef = React.createRef();
showData = () => {
console.log(this.myRef.current); // 即可获得将ref设置为this.myRef的节点
}
render() {
return (
<div>
<input ref={this.myRef} />
<button onClick={this.showData}>测试</button>
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'));
6、不要过度使用ref!
有时候ref可以直接通过event.target代替,在标签中使用的函数没有定义入参时,react会在调用函数 时,将事件源event作为回调函数的入参来调用,此时可以通过event.target获取当前触发事件的节点。
即:当发生事件的元素恰好是我们要操作的元素时,ref可以避免使用(通过event.target获取即可)
class Person extends React.Component {
myRef = React.createRef();
showData = (event) => {
// 即可获得目标阶段的节点
console.log(event.target);
}
render() {
return (
<div>
<input ref={this.myRef} />
<input onBlur={this.showData} />
</div>
)
}
}
ReactDOM.render(<Person />, document.getElementById('test'));
结语
以上是笔者整理的React基础入门语法,阅读过程中如有发现错漏,欢迎在评论区回复纠正!如在使用React过程中遇到难点或有趣的使用方案,也欢迎在评论区交流探讨!