文章目录
尚硅谷react教程+官方文档学习记录笔记01
jsx的介绍与语法
1.真实DOM和虚拟DOM
虚拟DOM本质是一般对象(Object对象),虚拟DOM的附带属性比真实DOM少,更轻量,虚拟DOM最终会被React转换成真实DOM显示在页面上。
javascript
<body>
<div id="h1">真实DOM</div>
<!-- ....省略引入react有关js文件,示范虚拟DOM渲染过程-->
<script type="text/babel">
const vdom = <h1> Hello JS <h1/>; // 虚拟DOM
ReactDOM.render(vdom,document.getElementById('h1')); // 将虚拟DOM渲染到页面
</script>
</body>
2.jsx语法
jsx语法是react定义的js+XML的语法,本质是React.creatElement(component,props,...children)的语法糖,用来简化和创建虚拟DOM。
javascript
// babel转译jsx成React.createElement()函数调用
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
); // 两段代码等效
语法规则一:定义虚拟DOM变量,赋值不写引号
javascript
// 例子 创建一个h1标签的虚拟DOM
var element = <h1> Hello JS </h1> // 最终产生一个JS对象
// 创建嵌套标签
var element = (
<h1>
<span> Hello JS </span>
</h1>
) // ()表示这一段为一个整体
语法规则二:标签中需要混入js表达式时使用{},表达式为可以接在=后的(let x = 表达式)
javascript
const data = "Hello JS";
var element = (
<h1>
<span> {data} </span>
<span> {data.toLowerCase()} </span>
</h1>
)
javascript
// 使用jsx动态显示列表,页面显示一个列表,有a,b,c三个项,每个li标签必须有唯一的key值,这里为数组的下标
const data = [ 'a','b','c'];
const element = (
<div>
<ul>
{
data.map((item,index)=>{
return <li key ={index}< {item} </li>
})
} // 这里的花括号里面是一个表达式
</ul>
</div>
)
语法规则三:样式的类名使用className,内联样式使用style={{key:value}}。
javascript
var element1 = <h1 className="title"> Hello JS </h1>
var element2 = <h1 style={{color:'white', fontSize:'20px'}}> Hello JS </h1> // 类似font-size由多个词连接的属性改为小驼峰写法
语法规则四:虚拟DOM只能有一个根标签
javascript
var h1 = (
<h1>
<span> {data} </span>
<span> {data.toLowerCase()} </span>
</h1>
) // h1为根标签
var span = (
<span> {data} </span>
<span> {data.toLowerCase()} </span>
) // 错误,有两个根标签span
语法规则五:标签必须闭合,单标签以"/"结尾或者使用闭合标签
<input />
<input ></input>
语法规则六:标签的首字母规范:
1)以小写字母开头,则转换为html同名标签,若html无对应标签,报错;
2)以大写字母开头,react渲染该组件,若组件未定义,报错;
语法规则七:使用{/* 内容 */}在jsx内写注释。应避免在jsx结构内写注释
var element = (<div>
{/* <h1>这是注释掉的标签</h1> */}
<h1>这是未注释标签</h1>
</div> )
模块与模块化,组件与组件化
模块与模块化
模块:提供特定js功能的程序,一般指一个js文件;将代码拆成模块可以简化js,提高js运行效率;
模块化:应用的js都以模块编写
组件与组件化
组件:实现局部功能效果代码的资源集合,包含html,css,js,image,video等,考虑复用性,简化项目编码,提高运行效率;
组件化:应用以多组件的方式实现
React组件
在Chorme里导入ReactDevTool插件;
React事件绑定
react把js原生事件onXXX方法重写了,变成小驼峰写法,比如onclick方法变为onClick方法,使用时需要注意。函数调用时使用{}包含函数名。
<button onclick="activateLasers()"></button> // 原生写法
<button onClick={activateLasers}></button> // react写法
函数式组件
定义一个函数,函数名首字母大写,返回虚拟DOM,调用react的render方法时,使用闭合标签<函数名/>作为第一个参数
javascript
// 函数组件
<body>
<div id="test"></div>
<!--引入react有关js文件-->
<script type="text/javascript" src="react.development.js"></script>
<script type="text/javascript" src="react-dom.development.js"></script>
<script type="text/javascript" src="babel.min.js"></script>
<script type="text/babel">
// 创建函数式组件
function Demo(){
return <h1> Hello JS <h1/>;
}
// 渲染页面。过程:React解析组件标签,寻找Demo组件,发现组件是函数Demo定义的,调用Demo函数,返回虚拟DOM
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>
</body>
类式组件
定义一个类,继承React.Component类,无变量需要传递获取,不写supper方法,必须写render方法,返回虚拟DOM
javascript
// 类组件
<body>
<div id="test"></div>
<!--....省略引入react有关js文件-->
<script type="text/babel">
// 创建类式组件
class Welcome extends React.Component {
render() {
return <h1>Hello</h1>;
}
}
// 渲染页面。过程:React解析组件标签,寻找Welcome组件,发现组件是类Welcome定义的,然后new一个Welcome实例对象,通过Welcome实例对象调用Welcome类里的render方法,返回虚拟DOM
ReactDOM.render(<Welcome/>,document.getElementById('test'));
</script>
</body>
简单组件(函数组件)与复杂组件(类组件)区别在于有无状态State
组件属性state
状态(state)驱动页面。
类组件对象实例自带state属性,值为null。当需要使用state取值时,借助构造器初始化state为对象,将需要的变量挂在state对象上。
javascript
// 初始化并获取state
<body>
<div id="test"></div>
<!--....省略引入react有关js文件-->
<script type="text/babel">
class Weather extends React.Component {
// 定义构造器
constructor(props){
super(props);
this.state = {isHot:false} // 借助构造器初始化state
}
render() {
return <h1>天气{this.state.isHot ? '热' : '凉'}</h1>; // 获取state
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>
</body>
javascript
// 实现点击事件
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {isHot:false};
this.changeWeather = this.changeWeather.bind(this); // 3. bind方法返回一个新函数,绑定在参数this上;这里bind(this)绑定了类的实例对象,这一行右边表示将类的changeWeather方法绑定在实例对象上,左边将绑定的函数取名为changeWeather
}
render() {
return <h1 onClick={this.changeWeather}>天气{this.state.isHot ? '热' : '凉'}</h1>; // 2.添加点击事件,这里相当于把类方法changeWeather作为onClick的回调,所以当用户点击时,this不是实例对象,且严格模式不能指向window,所以为undefined
}
// 点击事件
changeWeather(){
console.log(this); // 1.类中的方法默认开启局部严格模式,非对象实例调用时,this指向undefined
}
}
javascript
// 实现点击事件,理解以上注释部分
class Weather extends React.Component {
constructor(props){
super(props);
this.state = {isHot:false};
this.change = this.changeWeather.bind(this);
}
render() {
return <h1 onClick={this.change}>天气{this.state.isHot ? '热' : '凉'}</h1>;
}
// 点击事件
changeWeather(){
console.log(this);
}
}
state不能直接更改,更新要借助api setState()方法。
javascript
// 点击事件里更改state里的变量 -- 直接更改
changeWeather(){
const isHot = this.state.isHot; // 获取原来的isHot值
this.state.isHot = !isHot; // 点击后,借助开发插件可以看到this.state.isHot值没有变化,页面也无变化,react不允许直接改变状态
}
// 点击事件里更改state里的变量 -- 借助setState()方法
changeWeather(){
const i = this.state.isHot; // 获取原来的isHot值
this.setState({isHot:!i}); // 点击后,借助开发插件可以看到this.state.isHot值发生变化,页面也随state改变而改变
}
setState方法起到的是一个合并的动作。
// 原state
state = {a:1,b:2,c:0}
// 使用setState()更新
this.setState({a:99});
// 更新后的state为
state = {a:99,b:2,c:0}
精简state写法,避免有多属性和多方法需要绑定。在类组件的类里使用属性名+赋值语句+值/函数。赋值的函数定义必须使用箭头函数。
javascript
class Weather extends React.Component {
state = {isHot:false} // 往实例对象上挂一个state属性,值为一个对象,里面有isHot属性
render() {
return <h1 onClick={this.changeWeather}>天气{this.state.isHot ? '热' : '凉'}</h1>;
}
changeWeather = ()=>{
const i = this.state.isHot;
this.setState({isHot:!i});
} // 往实例对象上挂一个changeWeather属性,值为一个方法,方法里更新state。为什么使用箭头函数不使用function定义?因为箭头函数没有自己的this,箭头函数的this指向上下文的this,也就是实例对象
}
总结
1.state是对象,包含一个或多个key-value的组合,不能直接修改更新。
2.通过setState()方法更新state,从而从新渲染页面。
3.render()方法中的this指向的也是类组件的实例对象。
4.组件自定义方法的this为undefined时,在构造器里使用bind方法或使用赋值语句箭头函数改变this。
5.适用于用户数据交互渲染页面。
组件属性props
类组件对象实例自带props属性,值为{}。在ReactDOM.render方法内使用key="value"的方式传参,jsx内使用this.props.属性的方式接收。
javascript
// props的基本使用
class Person extends React.Component{
render(){
return (
<ul>
<li>name:{this.props.name}</li>
<li>sex:{this.props.sex}</li>
<li>age{this.props.age}</li>
</ul>
)
}
}
ReactDOM.render(<Person name="tom" age="18" sex="男"/>,document.getElementById('test')); // 这里传递的age为字符串
ReactDOM.render(<Person name="tom" age={18} sex="男"/>,document.getElementById('test')); // 这里传递的age为Number
传递多个props属性时,使用{}+扩展运算符"..."
javascript
// 传递多个props属性
class Person extends React.Component{
render(){
return (
<ul>
<li>name:{this.props.name}</li>
<li>sex:{this.props.sex}</li>
<li>age{this.props.age}</li>
</ul>
)
}
}
// 定义变量,包含需要的属性
const p = {name:"tom",age:"18",sex:"男"};
// 传递时使用{}
ReactDOM.render(<Person {...p}/>,document.getElementById('test'));
对props属性做限制,如参数类型,默认值,必要性
// 写法1--在类的外部
// 对类组件定义一个propTypes属性,属性内规定了props各个属性的类型
类名.propTypes = {
属性1: PropTypes.string.isRequired, // 使用isRequired标定为必要字段
属性2: PropTypes.number
}
// 对类组件定义一个defaultProps属性,属性内规定了props内各个属性的默认值
类名.defaultProps = {
属性1:'默认值',
属性2:'默认值'
}
// 写法2--在类的内部
class 类名{
static propTypes = {
属性1: PropTypes.string.isRequired,
属性2: PropTypes.number
}
static defaultProps = {
属性1:'默认值',
属性2:'默认值'
}
// ...state,render等
}
javascript
// ... 增加一个引入文件,以使用对标签属性增加限制的方法
<script type="text/javascript" src="prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component{
render(){
return (
<ul>
<li>name:{this.props.name}</li>
<li>sex:{this.props.sex}</li>
<li>age{this.props.age}</li>
</ul>
)
}
}
// 对标签属性进行类型和必要性限制
Person.propTypes = {
name: PropTypes.string.isRequired,
age:PropTypes.number
}
// 对标签属性指定默认值
Person.defaultProps = {
sex:'未知'
}
const p = {name:"tom",age:"18",sex:"男"};
ReactDOM.render(<Person {...p}/>,document.getElementById('test'));
</script>
对类组件传递方法
javascript
// 写法--在类的外部
Person.propTypes = {
speak: PropTypes.func
}
function speak(){
console.log('say something')
}
ReactDOM.render(<Person speak={speak}/>,document.getElementById('test'))
函数式组件使用props,组件三大属性中只有props可以在函数式组件使用。
function Person(props){
return (
<ul>
<li>name:{props.name}</li>
<li>sex:{props.sex}</li>
<li>age{props.age}</li>
</ul>
)
}
Person.propTypes = {
name: PropTypes.string.isRequired,
age:PropTypes.number
}
Person.defaultProps = {
sex:'未知'
}
ReactDOM.render(<Person name="tom" age={18} sex="男"/>,document.getElementById('test'))
总结
1.props属性只读,使用this.props.属性=值会报错。
2.组件标签的所有属性都保存在props中,通过标签属性从组件外向组件内传递变化的数据,组件内不要修改props数据。
3.适用于父传子,组件间通信。
组件属性ref
类组件对象实例自带refs属性,值为{}。类似原生html标签的id属性。
<标签 ref="属性名"></标签> // 在标签内定义
this.refs.属性名 // 使用
javascript
// refs基本使用
class Demo extends react.Component{
showData = ()=>{
console.log(this.refs); // {input1:input,button1:button,input2:input} input1是input标签的ref值,冒号后的input表示的是ref为input1的这一个input标签节点
console.log(this.refs.input1); // <input type="text" placeholder=""/>
}
render(){
return (
<div>
<input ref="input1" type="text" placeholder="点击按钮"/>
<button ref="button1" onClick="{this.showData}">点击提示左侧数据</button>
<input ref="input2" type="text" placeholder=""/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
以上为refs的字符串写法,ref属性都为字符串类型,会有效率问题。因此有了回调函数形式的ref。
<标签 ref={箭头函数}></标签> // 在标签内定义,在创建节点时自动调用箭头函数
<标签 ref={(a)=>{this.属性名=a}}></标签> // 在标签内定义,指定ref属性名
this.属性名 // 使用,因为直接挂在了实例对象上,所以可以直接使用this
javascript
// refs回调函数形式,使用内联函数
class Demo extends react.Component{
showData = ()=>{
console.log(this.input2.value)
}
render(){
return (
<div>
<input ref={(a)=>{console.log(a)}} type="text" placeholder="点击按钮"/> // 打印<input type="text" placeholder="点击按钮"/>
<input ref={(a)=>{this.input2=a}} type="text" placeholder=""/> // 获取当前所在的节点(a,也就是input标签),将这个节点挂在组件实例对象上,并命名为input2
<button ref="button1" onClick="{this.showData}">点击提示左侧数据</button>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
以上是使用内联函数的形式定义ref回调函数,内联函数在组件更新的时候会被执行两次,第一次传入参数null,第二次才传入DOM元素。因为每次调用render方法进行渲染时,都会创建一个新的函数实例,React会清空旧的ref然后设置一个新的,当旧的被清空,无法找到当前节点,所以第一次传入null。
javascript
// refs回调函数形式,使用内联函数,组件更新二次执行回调函数问题
class Demo extends react.Component{
state = {isHot:'hot'}
showData = ()=>{
alert(this.input2.value)
}
showWeather = ()->{
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
render(){
return (
<div>
<input ref={(a)=>{this.input2=a;console.log(a)}} type="text" placeholder=""/>
<button ref="button1" onClick="{this.showData}">点击提示左侧数据</button>// 点击提示数据按钮,内联函数调用一次,打印<input type="text" placeholder=""/>
<button onClick="{this.showWeather}">点击提示天气</button>// 点击显示天气按钮,内联函数调用两次,第一次打印null第二次打印<input type="text" placeholder=""/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
下面类绑定式的ref写法可以避免调用两次的情况,但使用内联函数调用两次的影响无关紧要,可继续使用。
函数名=(a)=>{this.属性名=a} // 在类组件内定义函数
<标签 ref=this.函数名></标签> // 在标签内定义
javascript
// refs回调函数形式,使用类绑定的形式,解决组件更新时二次执行回调函数问题
class Demo extends react.Component{
state = {isHot:'hot'}
showData = ()=>{
alert(this.input2.value)
}
showWeather = ()->{
const isHot = this.state.isHot;
this.setState({isHot:!isHot});
}
showInput = (a)=>{this.input2=a;console.log(a)}
render(){
return (
<div>
<input ref={this.showInput} type="text" placeholder=""/>
<button ref="button1" onClick="{this.showData}">点击提示左侧数据</button>// 点击提示数据按钮,内联函数调用一次,打印<input type="text" placeholder=""/>
<button onClick="{this.showWeather}">点击提示天气</button>// 点击显示天气按钮,内联函数不会被频繁调用
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
React.createRef调用后返回一个容器,容器内存储被ref标识的一个节点,每个被ref标识的节点都有专属自己的容器。
// 在类组件内,挂在类组件的实例对象上
myRef = React.createRef();
// 在虚拟DOM中
<input ref={this.myRef} type="text" />
javascript
// React.createRef的基本使用
class Demo extends React.Component{
myRef = React.createRef()
myRef2 = React.createRef()
showRef(){
console.log(this.myRef); // {current:input}
console.log(this.myRef.current); //<input type="text" />
console.log(this.myRef.current.value); // 输入框输入的值
}
render(){
return (
<div>
<input ref={this.myRef} type="text" />
<input ref={this.myRef2} type="text" />
<button onClick={this.showRef}>点击</button>
</div>
)
}
}
总结
- 应该尽量避免字符串的ref,使用内联回调函数的形式的ref。
- react推荐使用createRef,将ref绑定到组件实例上使用。
- ref适用于处理焦点、文本选择、媒体播放、触发动画、集成第三方DOM库。