React中创建组件
第一种 - 创建组件的方式
使用构造函数来创建组件,如果要接收外界传递的数据,需要在构造函数的参数列表中使用
props
来接收;(这种方式props只是形参,可以是其他名称)必须要向外return一个合法的JSX创建的虚拟DOM;
-
创建组件:
function Hello () { //如果在一个组件种return null,则该组件是空的 //return null; return <div>Hello 组件</div> }
-
为组件传递数据:
// 使用组件并为组件传递 props 数据,直接以属性的形式即可 <Hello name={dog.name} age={dog.age} gender={dog.gender}></Hello> // 在构造函数中,使用 props 形参,接收外界传递过来的数据 function Hello(props) { //在组件中,必须返回一个合法的JSX虚拟DOM元素 console.log(props) // props.name = 'zs' //组件中的props只读,不可以被重新赋值。Vue中也是一样 return <div>这是 Hello 组件 --- {props.name} --- {props.age} --- {props.gender}</div> }
- 父组件向子组件传递数据,可以使用{...obj}属性扩散传递的方式
<Hello {...dog}></Hello>
- es6展开运算符的举例
var o2 ={
age:22,
address:'中国北京',
phone:'110'
}
var o1 ={
name:'vivi',
...o2
}
console.log(o1);
- 将组件封装到单独的jsx文件中
import React from 'react'
// 在 function定义的组件中,如果想要使用 props,必须先定义形参,否则无法直接使用
// 但是,在class定义的组件中,可以直接使用 this.props 来直接访问,不需要预先接收 props
export default function Hello(props) {
// console.log(props)
return <p>haha --- {props.name}</p>
}
组件的名称首字母必须是大写,不然导入时会识别为html标签
在导入组件的时候,如何省略组件的
.jsx
后缀名:
// 打开 webpack.config.js ,并在导出的配置对象中,新增 如下节点:
resolve: {
extensions: ['.js', '.jsx', '.json'], // 表示这几个文件的后缀名可以省略不写。按顺序尝试,如果找不到js后缀的该文件,会去找jsx格式的
alias: {
'@': path.join(__dirname, './src') //@就可以表示项目根目录的src这一层目录
}
}
- 在导入组件的时候,配置和使用
@
路径符号
import Hello from '@/components/Hello'
第二种 - 创建组件的方式
使用 class 关键字来创建组件
ES6 中 class 关键字,是实现面向对象编程的新形式;
了解ES6中 class 关键字的使用
- class 中
constructor
的基本使用 - 实例属性和实例方法
- 静态属性和静态方法
- 使用
extends
关键字实现继承
用es5演示
//ES5中class关键字,是实现面向对象编程的新形式
function Person(name,age){
this.name = name
this.age = age
}
//实例方法
Person.prototype.say = function(){//挂载到构造函数原型对象上的方法,new出来的实例可以继承
console.log('这是Person的实例方法')
}
//静态方法
Person.show = function(){//实例访问不到,构造函数把它挂载到构造器上了,因此不能通过原型链继承
console.log('这是Person的静态方法')
}
const p1 =new Person('王多多',18);
console.log(p1);
//通过new出来的实例,访问到的属性,叫做【实例属性】
console.log(p1.name)
p1.say();
Person.show();
//【静态属性】:通过构造函数,直接访问到的属性
Person.info = 'aaa';//info属性直接挂载给了构造函数
console.log(p1.info)//undefined
用es6演示
//创建了一个类
//在class {} 内部,只能写构造器、静态方法、静态属性和实例方法
//class关键字内部,还是用原来的构造函数方法实现的。所以我们把class关键字称做语法糖
class Animal{
//这是类中的构造器,每一个类中都有一个构造器,如果我们没有手动编写构造器,那么实际上类内部有个看不见的构造器,类似于constructor(){}
//每当new这个类的时候,会立即执行构造器里的所有代码
constructor(name,age){
//实例属性
this.name = name
this.age = age
}
//在class内部,通过 static 修饰的属性,就是静态属性
static info = 'eee'//(今后用得不多)
run(){//挂载到类的原型对象上的方法,可以被实例继承,以后会经常用到
console.log('这是Animal的实例方法')
}
//这是静态方法,只要加了static,都直接挂载到构造器上(今后用得不多)
static show(){
console.log('这是Animal的静态方法')
}
}
const a1 =new Animal('二毛',5);
console.log(a1);
console.log(a1.age)//实例属性
console.log(a1.info)//undefined
console.log(Animal.info) //静态属性
a1.run();
Animal.show();
extends继承演示
父类中的任何属性和方法子类都能继承(包括静态的)
//父类,可以直接把父类理解成原型对象
class Person{
constructor(name,age){
this.name = name
this.age = age
}
sayHello(){//实例方法,子类都可以访问到这个方法
console.log('大家好')
}
}
//子类,在class类中,可以使用extends关键字,实现子类继承弗雷
//语法: class 子类 extends 父类
class American extends Person{
constructor(name,age){
//如果一个子类通过extends关键字继承了父类,那么在子类的构造器中必须先调用super,放在第一行
//super是一个函数,就是对父类构造器的一个引用
super(name,age);
}
}
const a1 =new American('Jack',25);
console.log(a1);
a1.sayHello();
//子类
class Chinese extends Person{
constructor(name,age,idNumber){
super(name,age);
this.idNumber = idNumber;
}
}
const c1 = new Chinese("张三",28,'330xxxxxxxxxxx0029')
console.log(c1);
c1.sayHello();
基于class关键字创建组件
最基本的组件结构:
//如果要使用 class 定义组件,必须让该组件继承自 React.Component
class 组件名称 extends React.Component {
//this.props直接接收外界传来的参数
console.log(this.props) //只读,不能重新赋值
// 在组件内部,必须有 render 函数,作用是渲染当前组件对应的 虚拟DOM结构
render(){
// render 函数中,必须返回合法的 JSX 虚拟DOM结构
return <div>这是 class 创建的组件{this.props}</div>
}
}
两种创建组件方式的对比
- 共同点:使用方式都是</>用标签形式,并且直接在标签内传参
使用 class 关键字创建的组件,有自己的私有数据(this.state) 和 生命周期函数;
使用 function 创建的组件,只有props,没有自己的私有数据和 生命周期函数;
- 用构造函数创建出来的组件:叫做“无状态组件”【今后用得不多,但是优点是因为剔除了生命周期函数,运行速度相对高一丢丢。电脑性能都较高,因此这个优势也不是很明显】
- 用class关键字创建出来的组件:叫做“有状态组件”【今后用得最多】
- 什么情况下使用有状态组件?什么情况下使用无状态组件?
- 如果一个组件需要有自己的私有数据,则推荐使用:class创建的有状态组件;
- 如果一个组件不需要有私有的数据,则推荐使用:无状态组件;
- React官方说:无状态组件,由于没有自己的state和生命周期函数,所以运行效率会比有状态组件稍微高一些;
有状态组件和无状态组件之间的本质区别就是:有无state属性、和 有无生命周期函数;
- 组件中的
props
和state/data
之间的区别- props 中的数据都是外界传递过来的;
- state/data 中的数据,都是组件私有的;(通过 Ajax 获取回来的数据,一般都是私有数据);
- props 中的数据都是只读的;不能重新赋值;
- state/data 中的数据,都是可读可写的;
渲染评论列表
通过for循环生成多个组件
- 数据:
CommentList: [
{ id: 1, user: '张三', content: '哈哈,沙发' },
{ id: 2, user: '李四', content: '哈哈,板凳' },
{ id: 3, user: '王五', content: '哈哈,凉席' },
{ id: 4, user: '赵六', content: '哈哈,砖头' },
{ id: 5, user: '田七', content: '哈哈,楼下山炮' }
]
4. 设置样式
-
JSX语法创建的DOM,如果用
style
写样式,不能用字符串赋值,需要用js对象来赋值
外面的{}
表示这是js代码,里面的{}
表示是个对象
如果属性值的单位是px,可以省略px光写数字<h1 style={ {color: 'red', fontWeight: 200, fontSize:16} }></h1>
-
启用 css-modules (css模块化)
先安装style-loader和css-loader
npm i style-loader css-loader -D
修改
webpack.config.js
这个配置文件,为css-loader
添加参数:
modules为true表示为普通的CSS样式表启用模块化
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',//打包处理CSS样式表的第三方loader
options:{modules:true}//开启CSS模块化
}
]
}
在做了这个处理后的css文件中,css 模块化只针对类选择器(.title)和Id选择器生效,不会将标签选择器(<h1>、<p>)模块化(仍然全局生效)
-
在需要的组件中,
import
导入样式表,并接收模块化的 CSS 样式对象:import cssObj from '../css/CmtList.css'
-
在需要的HTML标签上,使用
className
指定模块化的样式:<h1 className={cssObj.title}>评论列表组件</h1>
-
使用
localIdentName
自定义生成的类名格式,可选的参数有:- [path] 表示样式表
相对于项目根目录
所在路径 - [name] 表示 样式表文件名称
- [local] 表示样式的类名定义名称
- [hash:length] 表示32位的hash值
- 例子:
{ test: /\.css$/, use: [ {loader:'style-loader'}, { loader:'css-loader', options:{ modules: { localIdentName: '[path][name]-[local]-[hash:base64:5]', }, } } ] }
页面上展现出来的样式名是src-css-cmtlist-title-12gry这样的形式
- [path] 表示样式表
-
使用
:local()
和:global()
:local()
包裹的类名(默认可以不写,默认在样式表中定义的类名都是:local()
形式),是被模块化的类名,只能通过className={cssObj.类名}
来使用:global()
包裹的类名,是全局生效的,不会被css-modules
控制,定义的类名是什么,就是使用定义的类名className="类名"
/*被:global()包裹起来的类名,不会被模块化,而是全局生效*/
:global(.test) {
font-style: italic
}
- 注意:只有
.title
这样的类样式选择器,才会被模块化控制,类似于body
这样的标签选择器,不会被模块化控制;
在项目中启用模块化并同时使用bootstrap
把 自己的样式表,定义为
.scss
文件第三方的 样式表,还是 以
.css
结尾我们只需要为自己的
.scss
文件,启用模块化即可;运行
cnpm i sass-loader node-sass -D
安装能够解析scss
文件的loader-
添加loader规则:
{ test: /\.scss$/, use: ['style-loader', 'css-loader?modules&localIdentName=[path][name]-[local]-[hash:5]', 'sass-loader'] } // 打包处理 scss 文件的 loader
React 中绑定事件的注意点
事件的名称都是React的提供的,因此名称的首字母必须大写
onClick
、onMouseOver
-
为事件提供的处理函数,必须是如下格式
onClick= { function }
-
用的最多的事件绑定形式为:
<button onClick={ () => this.show('传参') }>按钮</button> // 事件的处理函数,需要定义为 一个箭头函数,然后赋值给 函数名称 show = (arg1) => { console.log('show方法' + arg1) }
在React中,如果想要修改 state 中的数据,推荐使用
this.setState({ msg:'xxx'})
绑定文本框与state中的值(单向数据流)
在 Vue 中,默认提供了
v-model
指令,可以很方便的实现数据的双向绑定
;但是,在 React 中,默认只是
单向数据流
,也就是只能把 state 上的数据绑定到页面,无法把页面中数据的变化,自动同步回 state ; 如果需要把 页面上数据的变化,保存到 state,则需要程序员手动监听onChange
事件,拿到最新的数据,手动调用this.setState({ })
更改回去;-
案例:
<input type="text" style={{ width: '100%' }} value={this.state.msg} onChange={() => this.textChanged()} ref="mytxt" /> // 响应 文本框 内容改变的处理函数 textChanged = () => { // console.log(this); // console.log(this.refs.mytxt.value); this.setState({ msg: this.refs.mytxt.value }) }
使用ref获取DOM元素引用
和 Vue 中差不多,vue 为页面上的元素提供了 ref
的属性,如果想要获取 元素引用,则需要使用this.$refs.引用名称
在 React 中,也有 ref
, 如果要获取元素的引用this.refs.引用名称
组件的生命周期
生命周期的概念:每个组件的实例,从 创建、到运行、直到销毁,在这个过程中,会出发一些列 事件,这些事件就叫做组件的生命周期函数;
-
React组件生命周期分为三部分:
- 组件创建阶段:特点:一辈子只执行一次
componentWillMount:
render:
componentDidMount:- 组件运行阶段:按需,根据 props 属性 或 state 状态的改变,有选择性的 执行 0 到多次
componentWillReceiveProps:
shouldComponentUpdate:
componentWillUpdate:
render:
componentDidUpdate:- 组件销毁阶段:一辈子只执行一次
componentWillUnmount:
vue中的生命周期图
React Native 中组件的生命周期
defaultProps
在组件创建之前,会先初始化默认的props属性,这是全局调用一次,严格地来说,这不是组件的生命周期的一部分。在组件被创建并加载候,首先调用 constructor 构造器中的 this.state = {},来初始化组件的状态。
React生命周期的回调函数总结成表格如下:
组件生命周期的执行顺序:
-
Mounting:
- constructor()
- componentWillMount()
- render()
- componentDidMount()
-
Updating:
- componentWillReceiveProps(nextProps)
- shouldComponentUpdate(nextProps, nextState)
- componentWillUpdate(nextProps, nextState)
- render()
- componentDidUpdate(prevProps, prevState)
-
Unmounting:
- componentWillUnmount()
通过Counter计数器的小案例 - 了解生命周期函数
给
props
属性提供默认值 和 进行类型校验,需要先运行cnpm i prop-types --save
-
给组件的
props
提供默认值// 为组件提供 默认的 props 属性值 static defaultProps = { initcount: 0 // 默认值为0 如果用户没有传递 ,则 默认就是0; 如果用户传递了,则 以用户传递的为准 }
-
给组件的
props
进行类型校验// 3. 进行 props 属性的类型校验, static propTypes = {} 是固定写法 static propTypes = { initcount: PropTypes.number.isRequired // 规定 外界在传递 initcount 的时候,必须是 number 值类型,否则 ,会在终端报警告 // isRequired 表示 这个 props 属性值 是必须要传递的 }
发表评论案例
相关文章
总结
掌握创建组件的两种方式
理解有状态组件和无状态组件的本质区别