React组件
组件的概念
在VUE中涉及到了组件的概念,在react中也是一样的概念。在传统的项目中,我们的页面都是以html的形势来进行开发,但是有很多内容是可以复用的,比如网站的头部和尾部,此刻我们就可以将这些内容抽取为组件,在任何一个页面中都可以复用这样就能达到代码的的复用。
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。本指南旨在介绍组件的相关理念。
React最强大的地方也是组件的开发,React按照可复用组件的概念来设计的。定义一个个的小组件,然后组装成大组件。所有的组件不论大小都是可复用的,即使跨项目也一样。一个组件,从形式上看就是一个普通的JS函数。
在React中我们定义组件有多种方式,目前可以分类为:
- 函数类型组件
- 类组件
函数组件
/**
* 函数类型的组件
*/
function Button(){
const template = (
<div>
<button type="submit">注册</button>
</div>
)
return template;
}
// 使用组件的时候,要采用标签的形式来进行引用
ReactDOM.render(
<Button />,
document.getElementById('root')
);
在上面的代码中,我们定义了一个组件Button,在ReactDOM.render函数里面调用这个组件的时候,我们需要使用标签的形式来引用这个组件<Button />
在组件中也可以引入其他的组件
function Delete() {
const template = (
<div>
<button type="reset">重置</button>
</div>
)
return template;
}
function Button() {
const template = (
<div>
<button type="submit">注册</button>
<Delete />
</div>
)
return template;
}
在Button组件中可以引入<Delete />
作为新的组件。
类组件
你同时还可以使用 ES6 的 class 来定义组件,基本语法为:
class Welcome extends React.Component {
render() {
return <h1>Hello</h1>;
}
}
上面这段代码就是定义了类组件,我们使用了ES6的语法来定义,React.Component就是默认定义好的组件对象,继承这个对象就可以完成组件的定义,并且默认调用render函数完成组件调用。
我们也可以将组件放在app.js文件里面编写,通过ES6的import导入到index.js中使用。
-
创建一个App.jsx文件,这个作为我们项目的根组件
import React from "react"; class Button extends React.Component{ render(){ return <h1>蜗牛学院</h1> } } export default Button;
-
在index.js文件中引入App.jsx组件
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App.jsx'; ReactDOM.render( <App />, document.getElementById('root') );
注意:App.js和App.jsx都是React中的组件,使用jsx我们在组件中写模板代码可以有提示,所以开发一般使用jsx后缀来定义我们的模板,这样开发起来更方便。
创建好的代码如下:
组件的样式和行为
在react的组件里面我们依然会操作样式和行为,在页面上组件进行渲染的时候一起渲染。
组件的样式
组件的样式主要有三种:
- 内联样式,主要通过style标签来渲染
- 普通样式,通过className或者id属性来定义
接下来我们就演示一下内联样式的设计:
class Button extends React.Component{
render(){
return <h1 style={{color:"red",fontSize:"20px"}}>蜗牛学院</h1>
}
}
这种样式定义方式就是直接在jsx模板上面填写style属性来定义。外层的{}是jsx的模板语法,内层{}代表包裹样式。多个样式需要使用,
来进行分割。
注意事项:
- 多个属性之间需要使用逗号来分割,不能使用分号,这是跟css样式的区别。
- 属性中不能出现
-
需要采用驼峰命名法来设计。 - 样式中的key可以不用引号,但是value需要使用引号括起来。
普通样式的设计:
普通样式设计主要就是通过外部css样式来设计。我们可以先在项目中src目类创建一个css文件夹,接着创建一个base.css文件。
.as {
width: 100px;
height: 100px;
background-color: red;
}
#show {
border-radius: 10px;
}
在app.jsx组件中引入这个样式文件
import React from "react";
//引入样式代码
import "./css/base.css"
class Button extends React.Component{
render(){
return (
<div>
<h1 style={{color:"red",fontSize:"20px"}}>蜗牛学院</h1>
<div className="as" id="show"></div>
</div>
)
}
}
在上面代码中,我们要使用外部文件中的class样式,组件里面需要使用className来表示class属性,这是jsx中需要注意的一个地方。
如果是普通样式,最终渲染出来的结果,样式会在页面的头部出现
组件的行为
组件的行为就是组件的事件处理
React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:
- React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
- 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。
传统的h5事件绑定:
<button onclick="activateLasers()">
Activate Lasers
</button>
在react中事件绑定方式:
<button onClick={activateLasers}>
Activate Lasers
</button>
activateLasers需要你在组件中定义这个方法
class Button extends React.Component{
add(){
console.log("点击成功");
}
render(){
return (
<div>
<h1 style={{color:"red",fontSize:"20px"}}>蜗牛学院</h1>
<div className="as" id="show" onClick={this.add}></div>
</div>
)
}
}
我们在class类里面定义了add函数用于执行事件的行为。
关于this问题
在组件中绑定事件,其中需要注意一个问题就是this指向的问题。
add(){
console.log("点击成功");
console.log(this);
}
如果直接在add中输出this,那输出的值为undefined,因为在普通函数里面this执行由调用的时候来确定的。那我们如果要将this执行组件本身,我们可以将函数改为箭头函数。
add = ()=>{
console.log("点击成功");
console.log(this);
}
使用箭头函数,我们输出this的时候指向就是Button组件。
我们还可以使用bind关键字来调用事件函数来完成this的传递和参数的传递
<div className="as" id="show" onClick={this.update.bind(this,"ok")}></div>
上面这种方式可以解决this的问题,也可以传递参数给函数。
也可以使用箭头函数来调用事件函数并传递参数。
<div className="as" id="show" onClick={()=>this.update("success")}></div>
箭头函数来绑定事件函数,可以在里面直接加上括号来调用,并且传递值给函数。
面试题:es5的函数和箭头函数中的this有什么区别。
组件的数据渲染
在组件中数据是需要动态更新,数据的来源主要有两部分,第一部分是外部的数据props属性来获取,第二部分是内部数据state,不管是外部的数据还是内部的数据我们都可以获取到过后进行动态的渲染。
外部数据props
首先我们需要准备两个组件,一个父组件,一个组件,在父组件中传递参数给子组件然后渲染。
Parent.jsx组件
import Child from "./Child"
export default class Parent extends Component {
render() {
return (
<div>
<Child obj={{username:"xiaowang",password:"123"}} initData="parent"/>
</div>
)
}
}
在父组件中,我们引入Child组件,在<Child>
组件中定义了两个属性obj和initData。这两个属性就是传递给子组件的数据。我们可以在子组件中获取数据。
export default class Child extends Component {
render() {
return (
<div>
{this.props.initData}
</div>
)
}
}
在Child组件中,我们可以使用this.props 来获取到父组件传过来的参数。props是react中非常重要的一个属性。无需定义,可以直接通过this对象来获取。
在Child中我们也可以定义一个构造函数,使用props作为参数来接收服务器传递回来的值。
export default class Child extends Component {
constructor(props){
super();
this.data = props;
}
render() {
return (
<div>
<p>{this.props.initData}</p>
<p>{this.data.obj.username}</p>
</div>
)
}
}
可以直接在构造器中定义一个参数名字为props来获取父组件传递过来的值。你也可以打印this获取到当前组件的内容,里面就包含了props这个对象。
组件数据的约束
随着你的应用程序不断增长,你可以通过类型检查捕获大量错误,PropTypes 提供一系列验证器,可用于确保组件接收到的数据类型是有效的。
接下来我们设计一个案列,来验证props的类型
import PropTypes from 'prop-types';
class WoniuComp extends React.Component {
render() {
const template = (
<div>
<ul>
<li>姓名:{this.props.filename}</li>
<li>年龄:{this.props.age}</li>
<li>特长:{this.props.specialty}</li>
</ul>
</div>
);
return template;
}
}
PropsTypes.defaultProps = {
username: '无名',
age: 20,
specialty: '无特长'
}
PropsTypes.propTypes = {
username: PropTypes.string.isRequired,
age: PropTypes.number.isRequired,
specialty: PropTypes.string
}
PropsTypes.defaultProps:可以设置props的默认值
PropsTypes.propTypes:可以设置props传递过来的默认数据格式。传递的值数据类型不满足要求,就会抛出警告
只读特性
关于props这个对象,我们可以在子组件中获取值,但是这个属性只读,不能对内容进行修改。为了避免对数据造成混乱,所以只读是有必要的。
handler = ()=>{
this.props.initData = "123";
}
render() {
return (
<div>
<p>{this.props.initData}</p>
<button onClick={this.handler}>点击加载</button>
</div>
)
}
在上面代码中,我们点击按钮后执行handler函数对props对象的值进行修改,这个操作是不被允许的。页面会出现报错信息。
handler = ()=>{
this.props.obj.username = "xiaofei"
}
如果我们要改的数据是对象里面的属性值,系统不会提示错误信息,但是页面数据也不会更改。
handler = ()=>{
// this.props.initData = "123";
this.props.obj.username = "xiaofei"
}
如果我们修改props里面对象的属性,那不会抛出异常,但是页面依然不会更新。除非我们显示的调用下面这句代码, 可以让组件的render方法重新渲染一次。但是一般不建议使用。
this.forceUpdate();
内部数据
我们都说React是一个状态机,体现是什么地方呢,就是体现在state上,通过与用户的交互,实现不同的状态,然后去渲染UI,这样就让用户的数据和界面保持一致了。state是组件的私有属性。
在React中,更新组件的state,结果就会重新渲染用户界面(不需要操作DOM),一句话就是说,用户的界面会随着状态的改变而改变。
在代码中我们先定义state
export default class State extends Component {
constructor(){
super();
// 在构造函数中定义state可以在创建组件的时候及初始化
this.state = {
username:"xiaofei"
}
}
// 在构造器外面也可以state
state = {
username:"xiaowang"
}
render() {
return (
<div>
{this.state.username}
</div>
)
}
}
state定义的数据就是当前组件自己的数据,可以来源于外部props,也可以是以后一部请求的结果。
我们也可能会对数据进行修改,但是要注意不能直接this.state.username="xxx"来进行修改。我们需要使用setState()来对state进行修改,并自动更新页面。
change = ()=>{
// 无效操作
// this.state.username = "xiaowu"
this.setState({
username:"xiaowu"
});
}
render() {
return (
<div>
<p>{this.state.username}</p>
<button onClick={this.change}>修改state</button>
</div>
)
}
通过调用setState()可以自动修改state值,并且自动渲染页面。这是修改state值唯一的方式。
使用setState()来修改值还有一下几个特点:
-
状态更新是异步的
用官方的话说就是this.props和this.state更新的时候可能是异步的,所以你不应该依靠他们的值来计算下一个状态,一般在react事件体系(包括react生命周期钩子函数)里面调用setState方法时,它是异步的,否则,它是
同步的。
为了解决异步的问题我们可以用以下的方案来解决
change = async ()=>{ await this.setState({ username:"xiaowu" }); console.log(this); }
在等待setState执行完毕后,在输出this来获取state的值
change = ()=>{ this.setState({ username:"xiaowu" },()=>{ console.log(this); }); }
我们也可以在setState中传递一个回调函数来获取执行后的结果。
-
state的更新是合并后的
当你调用 setState() ,React会合并你提供的对象到当前的 state 里。
代码如下:
state = { username:"xiaowang", password:"123" } change = ()=>{ this.setState({ username:"xiaowu" },()=>{ console.log(this); }); }
上面的代码state中的数据结构是两个属性,在调用setState的时候我们传递的对象会和state合并起来。不会出现覆盖。
列表渲染
在react中jsx可以渲染jsx数组,也就是数组里面的每一个对象都是jsx对象。比如:
<tbody>
{
[<tr>
<td>1</td>
<td>小米</td>
<td>北京</td>
<td>2300</td>
</tr>
]
}
</tbody>
在上面的案列中,你会发现{}中的数据类型为数组,里面保存数据就是jsx。所以我们称为jsx数组。我们在实际的案列中需要将数据动态的产生,数据往往不止一个tr。
数据:
state = {
data: [
{ id: 1, proName: "小米", address: "上海", price: 1200 },
{ id: 2, proName: "华为", address: "北京", price: 3400 },
{ id: 3, proName: "苹果", address: "成都", price: 4500 }
]
}
数据一旦定义好了,接下来我们就在页面上进行渲染
<tbody>
{
this.state.data.map((item,index)=>{
return
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.proName}</td>
<td>{item.address}</td>
<td>{item.price}</td>
</tr>
})
}
</tbody>
我们使用了map函数来对state数据进行遍历,将返回的jsx数组放在页面上进行渲染。可以发到vue中v-for的效果。并且需要注意遍历的过程中需要给tr加上key属性,这样有利于我们diff算法进行dom渲染。提高效率。
练习题:购物车
需求:商品的数量加减、总价和删除。
先定义数据的结构:
state = {
data: [
{ id: 1, proName: "小米", address: "上海", price: 1200,num:1 },
{ id: 2, proName: "华为", address: "北京", price: 3400,num:1 },
{ id: 3, proName: "苹果", address: "成都", price: 4500,num:1 }
]
}
数据渲染的代码:
<tbody>
{
this.state.data.map((item,index)=>{
return <tr key={item.id}>
<td>{item.id}</td>
<td>{item.proName}</td>
<td>{item.address}</td>
<td>
<button disabled={item.num===1} onClick={()=>this.change(-1,index)}>-</button>
{item.num}
<button onClick={()=>this.change(1,index)}>+</button>
</td>
<td>{item.price}</td>
</tr>
})
}
</tbody>
事件的绑定和数据的操作
// 数据修改
change = (num,index)=>{
const {data} = this.state;
data[index].num += num;
this.setState({data})
}
// 定义一个total属性,设置get方法来取值
get total(){
let price = 0;
this.state.data.forEach((item)=>{
price+=item.price * item.num;
})
return price;
}
一个是改变数量的函数,另外一个是计算总价。
条件渲染
在 React 中,你可以创建不同的组件来封装各种你需要的行为。然后,依据应用的不同状态,你可以只渲染对应状态下的部分内容。
React 中的条件渲染和 JavaScript 中的一样,使用 JavaScript 运算符 if 或者条件运算符去创建元素来表现当前的状态,然后让 React 根据它们来更新 UI。
if条件语句
我们可以使用if条件语句来判断,然后指定渲染的组件
export default class Container extends Component {
state = {
boo:true
}
toggle = ()=>[
this.setState({boo:!this.state.boo})
]
render() {
if(this.state.boo){
return <div>
<Header/>
<button onClick={this.toggle}>点击</button>
</div>
}
return <div>
<Content/>
<button onClick={this.toggle}>点击</button>
</div>
}
}
根据boo属性的值来决定渲染的组件是Header和Content
与运算符 &&
通过花括号包裹代码,你可以在 JSX 中嵌入任何表达式。这也包括 JavaScript 中的逻辑与 (&&) 运算符。它可以很方便地进行元素的条件渲染。
render() {
return (
<div>
{this.state.boo && <Content/> }
<button onClick={this.toggle}>点击</button>
</div>
)
}
&&是我们的短路运算符,一旦this.state.boo的值为false,那后面<Content>
的组件就不会渲染。
也可以使用三目运算符来判断显示的组件。
return (
<div>
{this.state.boo?<Header/>:<Content/> }
</div>
)
作者联系方式QQ:550250108