一.组件
当应用是以多组件的方式实现,这个应用就是一个组件化的应用
注意:
- 组件名必须首字母大写
- 虚拟DOM元素只能有一个根元素
- 虚拟DOM元素必须有结束标签
- 传递的参数,不能在组件中改动
二.函数式组件
//1.先创建函数,函数可以有参数,也可以没有,但是必须要有返回值 返回一个虚拟DOM
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//2.进行渲染
ReactDOM.Render(<Welcom name = "ss" />,document.getElementById("div"));
让我们来回顾一下这个例子中发生了什么:
1.调用 ReactDOM.render() 函数,并传入 2.<Welcome name="mint" />作为参数。 3.React 调用 Welcome 组件,并将 {name: 'mint'}作为 props 传入。 4.Welcome 组件将 Hello, mint 元素作为返回值。 5.React DOM 将 DOM 高效地更新为 Hello, mint。
三.类组件
//必须继承React.Component
//然后重写Render()方法,该方法一定要有返回值,返回一个虚拟DOM
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//渲染 【这个跟之前也是一样的】
ReactDOM.Render(<Welcom name = "ss" />,document.getElementById("div"));
执行过程:
1.React解析组件标签,找到相应的组件 2.发现组件是类定义的,随后new出来的类的实例,并通过该实例调用到原型上的render方法 3.将render返回的虚拟DOM转化为真实的DOM,随后呈现在页面中
四.组件案例
下面,我们通过一个案例更好的理解组件:【只关注与核心代码】
我们发现组件是可以包含中使用的, 而且如果创建的数组,必须要代一个key。数组元素中使用的 key 在其兄弟节点之间应该是独一无二的。然而,它们不需要是全局唯一的。当我们生成两个不同的数组时,我们可以使用相同的 key 值。
<script type="text/babel">
//创建一个组件<li>
function Li(props){
return <li>{props.value}</li>
};
// 1.创建类式组件<ul>
class MyComponent extends React.Component{
render() {
console.log(this.props.arr);
let com = this.props.arr.map((item,index)=>
//在这个地方包含了Li这个组件,【注意不能用{}】
//因为这个是一个列表,所以必须传递一个key【独一无二的Key】
//key 帮助 React 识别哪些元素改变了,比如被添加或删除。
<getLi value={item} key = {index} />
)
console.log(com);
return <ul>{com}</ul>
}
}
let num = [1,2,3,4]
//2.渲染组件
ReactDOM.render(<MyComponent arr={num}/>,document.getElementById("test");
</script>
五.组件实例的三大属性
1.state
我们都说React是一个状态机,体现是什么地方呢,就是体现在state上,通过与用户的交互,实现不同的状态,然后去渲染UI,这样就让用户的数据和界面保持一致了。state是组件的私有属性。
在React中,更新组件的state,结果就会重新渲染用户界面(不需要操作DOM),一句话就是说,用户的界面会随着状态的改变而改变。
state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
1.1案例:
1.需求:页面显示【今天天气很炎热】,鼠标点击文字的时候,页面更改为【今天天气很凉爽】
核心代码如下:
<body>
<!-- 准备好容器 -->
<div id="test">
</div>
</body>
<!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js"></script>
<!--这里使用了js来创建虚拟DOM-->
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
//构造器调用几次?—— 1次
constructor(props){
console.log('constructor')
super(props);
//先给state赋值
this.state = {isHot:true,wind:"微风"};
//找到原型的changeWeather,根据changeWeather函数创建了一个dem1的函数,并且将实例对象的this赋值过去
this.changeWeather = this.changeWeather.bind(this);
}
//render调用几次?—— 1+n次 1是初始化那次 n是状态更新次数
render(){ //这个This也是实例对象
console.log('render')
//如果加changeWeather(),就是将函数的回调值放入这个地方
//this.changeWeather这里面加入this,并不是调用,只不过是找到了dem这个函数,在调用的时候相当于直接调用,并不是实例对象的调用
return <h1 onClick = {this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"},{wind}</h1>
}
//通过state的实例调用dem的时候,this就是实例对象
//changeWeather调用几次? —— 点击此调用几次
changeWeather(){
console.log('changeWeather')
const state = this.state.isHot;
//状态中的属性不能直接进行更改,需要借助API
// this.state.isHot = !isHot; 错误
//必须使用setState对其进行修改,并且这是一个合并
this.setState({isHot:!state});
}
}
// 2.渲染,如果有多个渲染同一个容器,后面的会将前面的覆盖掉
ReactDOM.render(<Weather />,document.getElementById("test"));
</script>
需要注意的是:
1.组件的构造函数,必须要传递一个props参数
2.特别关注this【重点】,类中所有的方法局部都开启了严格模式,如果直接进行调用,this就是undefined
3.想要改变state,需要使用setState进行修改,如果只是修改state的部分属性,则不会影响其他的属性,这个只是合并并不是覆盖。
this.setState(),该方法接收两种参数:对象或函数。
对象:即想要修改的state 函数:接收两个函数,第一个函数接受两个参数,第一个是当前state,第二个是当前props,该函数返回一个对象,和直接传递对象参数是一样的,就是要修改的state;第二个函数参数是state改变后触发的回调 在此还需要注意的是,setState有异步更新和同步更新两种形式,那么什么时候会同步更新,什么时候会异步更新呢?
React控制之外的事件中调用
setState是同步更新的。比如原生js绑定的事件,setTimeout/setInterval等。大部分开发中用到的都是React封装的事件,比如
onChange、onClick、onTouchMove等,这些事件处理程序中的setState都是异步处理的。
//1.创建组件
class Weather extends React.Component{
//可以直接对其进行赋值
state = {isHot:10};
render(){ //这个This也是实例对象
return <h1 onClick = {this.changeWeather}>点击事件</h1>
}
//箭头函数 [自定义方法--->要用赋值语句的形式+箭头函数]
changeWeather = () =>{
//修改isHot
this.setState({ isHot: this.state.isHot + 1})
console.log(this.state.isHot);
}
}
上面的案例中预期setState使得isHot变成了11,输出也应该是11。然而在控制台打印的却是10,也就是并没有对其进行更新。这是因为异步的进行了处理,在输出的时候还没有对其进行处理。
componentDidMount(){
document.getElementById("test").addEventListener("click",()=>{
this.setState({isHot: this.state.isHot + 1});
console.log(this.state.isHot);
})
}
但是通过这个原生JS的,可以发现,控制台打印的就是11,也就是已经对其进行了处理。也就是进行了同步的更新。
React怎么调用同步或者异步的呢?
在 React 的 setState 函数实现中,会根据一个变量 isBatchingUpdates 判断是直接更新 this.state 还是放到队列中延时更新,而 isBatchingUpdates 默认是 false,表示 setState 会同步更新 this.state;但是,有一个函数 batchedUpdates,该函数会把 isBatchingUpdates 修改为 true,而当 React 在调用事件处理函数之前就会先调用这个 batchedUpdates将isBatchingUpdates修改为true,这样由 React 控制的事件处理过程 setState 不会同步更新 this.state。
如果是同步更新,每一个setState对调用一个render,并且如果多次调用setState会以最后调用的为准,前面的将会作废;如果是异步更新,多个setSate会统一调用一次render
changeWeather = () =>{
this.setState({
isHot: 1,
cont:444
})
this.setState({
isHot: this.state.isHot + 1
})
this.setState({
isHot: 888,
cont:888
})
}
上面的最后会输出:isHot是888,cont是888
changeWeather = () =>{
this.setState({
isHot: this.state.isHot + 1,
})
this.setState({
isHot: this.state.isHot + 1,
})
this.setState({
isHot: this.state.isHot + 888
})
}
初始isHot为10,最后isHot输出为898,也就是前面两个都没有执行。
注意!!这是异步更新才有的,如果同步更新,每一次都会调用render,这样每一次都会更新。
1.2简化版本:
- state的赋值可以不再构造函数中进行
- 使用了箭头函数,将this进行了改变
<body>
<!-- 准备好容器 -->
<div id="test">
</div>
</body>
<!-- 引入依赖 ,引入的时候,必须就按照这个步骤-->
<script src="../js/react.development.js" type="text/javascript"></script>
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<script src="../js/babel.min.js"></script>
<script type="text/babel">
class Weather extends React.Component{
//可以直接对其进行赋值
state = {isHot:true};
render(){ //这个This也是实例对象
return <h1 onClick = {this.changeWeather}>今天天气很{this.state.isHot?"炎热":"凉爽"}</h1>
//或者使用{()=>this.dem()也是可以的}
}
//箭头函数 [自定义方法--->要用赋值语句的形式+箭头函数]
changeWeather = () =>{
console.log(this);
const state = this.state.isHot;
this.setState({isHot:!state});
}
}
ReactDOM.render(<Weather />,document.getElementById("test"));
</script>
如果想要在调用方法的时候传递参数,有两个方法:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
上述两种方式是等价的,分别通过箭头函数和 Function.prototype.bind 来实现。
在这两种情况下,React 的事件对象 e 会被作为第二个参数传递。如果通过箭头函数的方式,事件对象必须显式的进行传递,而通过 bind 的方式,事件对象以及更多的参数将会被隐式的进行传递。
2.props
基本使用:
<body>
<div id = "div"></div>
</body>
<script type="text/babel">
class Person extends React.Component{
render(){
return (
<ul>
//接受数据并显示
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
//传递数据
ReactDOM.render(<Person name="mint" age = "18" sex="男"/>,document.getElementById("div"));
</script>
如果传递的数据是一个对象,可以更加简便的使用
<script type="text/babel">
class Person extends React.Component{
render(){
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
}
const p = {name:"mint",age:"18",sex:"男"}
ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
</script>
... 这个符号恐怕都不陌生,这个是一个展开运算符,主要用来展开数组,如下面这个例子.
arr = [1,2,3];
arr1 = [4,5,6];
arr2 = [...arr,...arr1]; //arr2 = [1,2,,3,4,5,6]
但是他还有其他的用法:
1.复制一个对象给另一个对象{...对象名}。此时这两个对象并没有什么联系了
const p1 = {name:"张三",age:"18",sex:"女"}
const p2 = {...p1};
p1.name = "sss";
console.log(p2) //{name:"张三",age:"18",sex:"女"}
2.在复制的时候,合并其中的属性
const p1 = {name:"张三",age:"18",sex:"女"}
const p2 = {...p1,name : "111",hua:"ss"};
p1.name = "sss";
console.log(p2) //{name: "111", age: "18", sex: "女",hua:"ss"}
注意!! {...p}并不能展开一个对象
props传递一个对象,是因为babel+react使得{...p}可以展开对象,但是只有在标签中才能使用
对于props限制
很多时候都想要传递的参数进行相应的限制,比如:限制传递参数的类型,参数的默认值等等
react对此提供了相应的解决方法:
propTypes:类型检查,还可以限制不能为空defaultProps:默认值
<script type="text/babel">
class Person extends React.Component{
render(){
//props是只读的
return (
<ul>
<li>{this.props.name}</li>
<li>{this.props.age}</li>
<li>{this.props.sex}</li>
</ul>
)
}
//对组件的属性对其进行限制
static propTypes = {
name:PropTypes.string.isRequired, //限定name是string类型,并且必须要传递
sex:PropTypes.string, //限定sex是string类型
speak:PropTypes.func //限定speak是function类型
}
//指定默认的标签属性
static defaultProps = {
sex:"不男不女",
age:18
}
}
//在js中可以使用{...p}来复制一个对象,但是这个地方并不是复制对象,而是babel+react通过展开运算符,展开了一个对象
//但是只能在标签中进行使用
//const p = {name:"张三",age:"18",sex:"女"} {14}就代表的是数值
//ReactDOM.render(<Person {...p}/>,document.getElementById("div"));
ReactDOM.render(<Person name="sss" age = {14} speak="8"/>,document.getElementById("div"));
function speak(){
console.log("这个是一个函数")
}
</script>
</html>
函数式组件的使用:
函数在使用props的时候,是作为参数进行使用的(props);
function Person(props){
return (
<ul>
<li>{props.name}</li>
<li>{props.age}</li>
<li>{props.sex}</li>
</ul>
)
}
3.refs
Refs 提供了一种方式,允许我们访问 DOM 节点或在 render 方法中创建的 React 元素。
Refs主要提供了三种方式:
1.字符串形式
在想要获取到一个DOM节点,可以直接在这个节点上添加ref属性。利用该属性进行获取该节点的值。
案例:给需要的节点添加ref属性,此时该实例对象的refs上就会有这个值。就可以利用实例对象的refs获取已经添加节点的值
<input ref="dian" type="text" placeholder="点击弹出" />
inputBlur = () =>{
alert(this.refs.shiqu.value);
}
2.回调形式
回调形式会在ref属性中添加一个回调函数。将该DOM作为参数传递过去。
如:ref里面就是一个回调函数,self就是该input标签。然后在将该DOM元素赋值给实例对象中的一个属性
<input ref={self =>{ this.dian = self;console.log(self)}} placeholder="点击弹出" />
input 也可以将函数提取出来,在ref中进行调用
isRef = (self) => {
this.dian = self;
console.log(self)
}
<input ref={this.isRef} type="text" placeholder="点击弹出" />
3.API形式
React其实已经给我们提供了一个相应的API,他会自动的将该DOM元素放入实例对象中
如下:依旧先在DOM元素中添加一个ref元素
{/*<input ref={this.容器名称} type="text" placeholder="点击弹出" />*/}
<input ref={this.MyRef} type="text" placeholder="点击弹出" />
<input ref={this.MyRef1} type="text" placeholder="点击弹出" />
通过API,创建React的容器,相当于省略了回调的中间环节。但是这个容器是专门专用的,所以每一个ref都需要创建这个。该API会将DOM元素赋值给实例对象的名称为容器名称的属性的current【这个current是固定的】
{/*容器名称 = React.createRef()* /} MyRef = React.createRef(); MyRef1 = React.createRef(); API
然后就可以使用了 然后就可以使用了
btnOnClick = () =>{
//创建之后,将自身节点,传入current中
console.log(this.MyRef.current.value);
}
官方提示我们不要过度的使用ref,如果发生时间的元素刚好是需要操作的元素,就可以使用事件去替代。
六.参考
React官网:react.docschina.org/
ES6入门教程: es6.ruanyifeng.com/#docs/class