课程原视频:https://www.bilibili.com/video/BV1wy4y1D7JT?p=2&spm_id_from=pageDriver
目录
一、React 概述
二、React 入门
三、面向组件编程
四、收集表单数据
五、高阶函数_函数柯里化
学前需掌握以下知识点
用于构建用户界面的 Javascript 库,它主要专注于界面与视图。采用组件化模式、声明式编码,提高开发效率及组件复用率。在React Native中可以使用React语法进行移动端开发。使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互。
由Facebook开发,且开源
起初由 Facebook的软件工程师 Jordan Walke 创建
于2011年部署于Facebook的newsfeed
随后在2012年部署于Instagram
2013年5月宣布开源
…
近十年“陈酿”被各大厂广泛使用
模块
随着业务逻辑增加,代码越来越多且复杂。人们更倾向于将复杂大块的业务逻辑拆分成小模块,每个模块复杂一部分内容。可以理解为向外提供特定功能的js程序,一般就是一个js文件。这样写的好处是复用js,简化了js的编写,提高了js运行效率
声明式
React 使创建交互式 UI 变得轻而易举。为你应用的每一个状态设计简洁的视图,当数据变动时 React 能高效更新并渲染合适的组件。
以声明式编写 UI,可以让你的代码更加可靠,且方便调试。
组件
用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)。一个界面的功能太复杂,而且资源浪费也很多。React将各个不同的功能拆分为组件,每个组件只负责特定区域中的数据展示,如Header组件只负责头部数据展示。这样写可以复用代码,简化项目编码,提高运行效率
组件化
构建管理自身状态的封装组件,然后对其组合以构成复杂的 UI。
由于组件逻辑使用 JavaScript 编写而非模板,因此你可以轻松地在应用中传递数据,并保持状态与 DOM 分离。
当我们需要修改DOM属性时,真实DOM是将一个新的界面直接覆盖在旧界面上,原来页面上已经有的数据就浪费了,假如原来有100条数据,当数据发生变化了就得产生100+n个DOM
而React 怎么做的呢,当数据发生变化时,将真实DOM生成对应虚拟DOM,但并不会将原来的虚拟DOM丢弃,它会进行虚拟DOM的比较,如果一样的话就不会给他生成真实的DOM,同样100条数据,发生变化了,它只会渲染多出来的数据
关于虚拟DOM
本质是Object类型的对象(一般对象)。虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是React内部在用,无需真实DOM上那么多属性。虚拟DOM最终会转换成真实DOM,呈现在页面上。
准备工作
1.创建项目文件
2.下载依赖包
链接:https://pan.xunlei.com/s/VN3NvR6dEbf7OToBaxa14HWYA1?path=ids6
3.引入依赖包
将上面三个文件托到我们项目中,注意!引用必须按照以下顺序进行
<! -- 引入react核心库-->
<script type="text/javescript" src=" ../React学习/季s/react.development. js"> </script>
<l --引入react-dom,用于支持react操作DOM -->
<script type="text/javescript" src="../React学习/3s/react-dom.development.js"></script>
<! --引入babel.用于将Jsx转为js-->
<script type="text/javescript" src="../React 学习/Js/babel.min.js"></script>
引入react.development 、 react-dom.development 这两个库后,全局变量中多了React 和 ReactDOM两个变量。
开始写代码
<script type="text/babel">/*此处一定要写babel */
//1.创建虚拟DOM
const VDOM = <h1>Hello,React</h1>/* 此处一定不要写引号,因为不是字符串*/
//2.渲染虚拟DOM到页面
ReactDOM.render(VDoM,document.getElementById( 'test' ))
</script>
script标签的type属性必须写text/babel,如果不写默认为JavaScript
运行效果
在React 中的语法是JSX 并不是JS 有一定区别。
①:定义虚拟DOM时,不要使用引号
const VDOM = (
<h2 id="myId">
<span>myData</span>
</h2>
)
这是个错误示范,这样输出的结果就是,myData还是有,但h2标签id为空
②:标签中混入JS表达式时要用{}
const VDOM=(
<h2 id={myId.toLowerCase()}>
<span>{myData.toLowerCase()}</span>
</h2>
)
toLowerCase()这个API是将ID字符都变成小写的意思
③:样式的类名指定不要使用class,而是className
.title{
color:white;
}
const VDOM=(
<h2 calssName="title" id={myId.toLowerCase()}>
<span>{myData.toLowerCase()}</span>
</h2>
)
因为class是ES6编码格式中的关键字,JSX为了避免发生冲突于是便这么写。像这样的设计React中还有许多
④:内联样式,要用style={{key:value}}的形式去写,否则直接报错
const VDOM=(
<h2 calssName="title" id={myId.toLowerCase()}>
<span style={{color:'while',fontsize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
)
⑤:只有一个根标签
向上面这样写就会飘红,因为跟标签是(h2)并且有两个,需要一个容器给包起来
const VDOM=(
<div>
<h2 calssName="title" id={myId.toLowerCase()}>
<span style={{color:'while',fontsize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
<h2 calssName="title" id={myId.toLowerCase()}>
<span style={{color:'while',fontsize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
</div>
)
⑥:标签必须闭合
与Html中的标签同理,必须有头有尾,或者写成自结束标签,如<input/>
⑦:标签首字母
React中非常讲究细节。
1)若小写字母开头
2)若大写字母开头
JSX中写注释格式 {/ 代码块 /}
JSX语法小练习
需求: 动态展示如下列表
//模拟一些数据
const data=['Angular','React','Vue']
const VDOM=(
<div>
<h1>前端js框架列表</h1>
<ul>
{//还记得上面的JSX语法规则嘛:标签中混入JS表达式时要用{}
//但是并没有说JS代码哦,所以这里我们for循环直接传值写
//map(vaule,key)加工数组
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方,如
(1). a
(2). a+b
(3). demo(1)
(4). arr.map()
(5). function test () {}
语句(代码):
(1) . if() {}
(2) . for() {}
(3) . swich() {case:xxxx}
React 接收到数组数据时会自动帮我们遍历,如果传的是对象会报错
Object are not valid as a React child
官方给了我们两种组件化编程的方式:
从简到难。我们回顾一下什么是组件,组件:用来实现局部功能效果的代码和资源的集合,那一个组件里面就得包含 布局(html)、样式(css)、交互(js)、资源(image)等等。
所谓函数式组件如字面意思,使用函数的形式编写组件。该组件具有函数的一些特征
<script type="text/babel">
function MyComponent(){
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
ReactDom.render(<MyComponent/>,document.getElementById('test'))
/*
执行ReactDom.render(<MyComponent/>,document......之后发生什么?
1.React解析组件标签,找到MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数
3.将返回的虚拟DOM转化为真实DOM,随后呈现在页面中
*/
</script>
注意事项
在学习类式组件之前我们先复习一下类的基本知识
类中的构造器不是必须写的,要对实例进行一些初始化的操作,如添加指定属性时才写
如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须要调用的
类中所定义的方法,都放在了类的原型对象上,供实例去使用
注意事项
state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)。
组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)
案例:如图点击后改变天气
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
constructor(props){//创建构造器
super(props)
//初始化状态
this.state = {isHont:false} //state值一般转的是对象
}
render(){//重写父类的render()方法
return <h1>今天天气很{this.state.isHont ? '炎热' : '凉爽'}</h1>
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
}
</script>
上面的代码初始化了组件状态,通过判断isHont值就能判断返回炎热还是凉爽,现在我们只需要改变isHont值就可以完成上面的需求了,那如何改变isHont的值呢?
React中如何绑定事件
【复习】原生的三种事件绑定方法都可以进行事件判定,React官方推荐使用函数式绑定。类方法定义在类的原型对象上,供实例使用,通过类实例调用方法时,方法中的 this 指向的就是类实例。类中定义的方法在局部都开启了严格模式,直接调用不会指向window,所以值为undefined
React 不支持直接修改状态的属性,就算修改了React 本身也不作反馈
this.state. isHot = !isHot
需要借助setState这个API去更改
<script type="text/babel">
//1.创建组件
class Weather extends React.Component{
constructor(props){//创建构造器
super(props)
//初始化状态
this.state = {isHont:false}
//解决changWeather中this指向问题
this.changWeather=this.changWeather.bind(this)
}
render(){//重写父类的render()方法
//读取状态
const {isHot}=this.state
return <h1 onClick={this.changWeather}>今天天气很{isHont ? '炎热' : '凉爽'}</h1>
}
changWeather(){
//获取原来的isHot值
const isHot=this.state.isHot
//严重注意:状态必须通过setState进行更改,且更新是一种合并,并不是替换
this.setState({isHot:!isHot});
}
//2.渲染组件到页面
ReactDOM.render(<Weather/>,document.getElementById('test'))
}
</script>
this.setState 这个API操作是合并操作,而不是替换。构造器只在new实例时调用,render在每次状态更新和初始化的时候调用,只要我们通过合法的方式(this.setState API)更新组件的状态,React会自己帮我们调用render方法更新组件
state 的简写方式
【复习】类中可以直接写赋值语句
<script type="text/babel">
class Weather extends React.Component{
//1.初始化状态
state = {isHot:false,wind:'微风'}
render(){
const {isHot}=this.state
return <h1 onClick={this.changWeather}>今天天气很{isHont ? '炎热' : '凉爽'}</h1>
}
//自定义方法——要用赋值语句的形式+箭头函数
changWeather = ()=> {
const isHot=this.state.isHot
this.setState({isHot:!isHot});
}
ReactDOM.render(<Weather/>,document.getElementById('test'))
}
</script>
【复习】箭头函数没有自己的this,所以他会往外部找this,所以函数里的this指向的其实Weather构造的实例对象
每个组件对象中对会含有props属性。组件标签的所有属性都保存在props中。通过标签属性从组件外向组件内传递变化的数据。组件内部不建议修改props的数据,数据的更新借助于state。
基本使用
需求: 自定义用来显示一个人员信息的组件
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
console.log(this);
const{name,age,sex}=this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="Tom" sex="女" age="18">,document.getElementById('test1'))
ReactDOM.render(<Person name="JACK" sex="男" age="17">,document.getElementById('test2'))
</script>
在渲染组件时传入值,React会直接将其存在props属性上,但考虑到一个问题,如果某个对象属性非常多这样写就不是很聪明
批量传递
<script type="text/babel">
//创建组件
class Person extends React.Component{
render(){
console.log(this);
const{name,age,sex}=this.props
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
}
const p={name:'老刘',age:18,sex:'女'}
//渲染组件到页面
ReactDOM.render(<Person {...p}>,document.getElementById('test2'))
</script>
对 props 进行限制
只有在Js原生的环境下我们才会谈数据类型,以及数据类型的判断,所以我们如果想传Number类型的数据到props时,要像下面这样写
ReactDOM.render(<Person age:{18}>,document.getElementById('test2'))
当我们的组件给别人使用时,别人不知道该往组件里传什么类型的属性,所以我们需要对props进行一些限制,React底层帮我们写好了我们需要按指定格式限制属性类型就可以了
Person.propTypes{
name:React.PropTypes.string
}
这种方式已经在React 15.xxxx 版本时被弃用了,16.xxx 版本需要引入依赖包prop-types.js
它有什么用呢?—— 用于对组件标签属性进行限制,下好然后在项目中引用
Person.propTypes{
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
}
Person.defaultProps{
sex:"男",
age:18
}
Person.propTypes 对标签属性类型、必要性进行限制
语法:[属性名]:PropTypes.[数据类型].[必要性描述]
注意:数据类型都避开了原生的属性String、Number
Person.defaultProps 设置默认标签属性值
props 的简写方式
【注意】props是只读属性,只能get,不能set
<script type="text/babel">
//创建组件
class Person extends React.Component{
static propTypes{
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
}
static defaultProps{
sex:"男",
age:18
}
render(){}}
</script type="text/babel">
关于类式构造器传不传props
类中构造器可写可不写,如果写了构造器constructor必调super函数,而构造中传不传props取决于你需不需要在构造器中通过this访问props,必接必传
数式组件使用 props
<script type="text/babel">
//创建组件
funciton Person(props){
//限制标签类型和必要学
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//函数式组件想使用限制器只能在外部设置
Person.propTypes{
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number,
}
//默认值
Person.defaultProps{
sex:"男",
age:18
}
//渲染组件到页面
ReactDOM.render(<Person name='Tom' sex='男' age={18}/>,document.getElementById('test2'))
</script>
关于组件的三大核心属性,函数式组件只能使用props
组件内的标签可以定义ref属性来标识自己。
字符串形式的ref
<script type="text/babel">
class Demo extends.React.Component{
showData()=>{
const {input1}=this.refs
alert(input1.value)
}
showData2 =()=>{
const {input2}=this.refs
alert(input2.value)
}
render(){
return
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button ref="button100" onClick={this.showData}>点我提示左侧数据</button>
<input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
}
}
</script>
组件里的标签可以通过ref属性来标识自己,然后都会收集到类实例的refs属性中,相当于原生中的id,但我们拿去值的方式也不原生中的document.getElementById,而是const{key值}=this.refs
【注意】 字符串的ref存在一些效率问题,如果写多了效率就不高,但方式简单,不过还是建议使用createRef API 和回调函数的ref
回调函数式的ref
<script type="text/babel">
class Demo extends.React.Component{
showData()=>{
const {input1}=this.refs
alert(input1.value)
}
showData2 =()=>{
const {input2}=this.refs
alert(input2.value)
}
render(){
return
<div>
<input ref={a => this.input1=a} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={a => this.input2=a} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
}
}
</script>
——关于回调函数的回调次数问题
在组件初始化的时候会执行一次,传入的是 DOM 元素
每次更新组件的时候都会调用两次回调函数,第一次传入值为null,第二次才传入参数DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但大多数情况下它是无关紧要的
<script type="text/babel">
class Demo extends.React.Component{
saveInput=(c)=>
{
this.input=c;
conosle.log('@',c);
}
render(){
return
<div>
<input ref={this.saveInput} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
}
}
</script>
createRef 的使用
<script type="text/babel">
class Demo extends.React.Component{
myRef=React.createRef()
myRef2=React.createRef()
//展示左侧输入框的数据
showData =()=>{
console.log(this.myRef.current.value);
}
//展示右侧输入框的数据
showData2 =()=>{
console.log(this.myRef2.current.value);
}
render(){
return
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧数据</button>
<input ref={this.myRef2} onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
}
}
</script>
【注意】React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是“专人专用”的,只能存一个。这种方法繁琐的地方在于每次都要定义一个容器接受返回值,但也是官方最推荐的写法
需求: 定义一个包含表单的组件,输入用户名密码后, 点击登录提示输入信息
<script type="text/babel">
class Login extends React.Component{
handleSubmit =(event)=>{//event 事件对象
event.preventDefault()//阻止表单提交
const {username,password}=this
alert(`你输入的用户名是:{username.value} 密码是:{password}`)
}
render(){
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名: <input ref={c=>this.username=c}type="text" name="username"/>
密码: <input ref={c=>this.password=c}type="password" name="password"/>
<button>登录</button>
</from>
)
}
ReactDOM.render(<Login/>,document.getElementById('test'))
}
</script>
非受控组件指的是,表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值。
在非受控组件中,可以使用一个ref来从DOM获得表单值。
非受控组件在底层实现时是在其内部维护了自己的状态state,这样表现出用户输入任何值都能反应到元素上。
<script type="text/babel">
class Login extends React.Component{
handleSubmit =(event)=>{//event 事件对象
demo=(exent)=>{
this.setState({username:event.trage})
conosle.log("@");
}
}
render(){
return (
<form action="http://www.baidu.com" onSubmit={this.handleSubmit}>
用户名: <input onChange={this.demo} type="text" name="username"/>
密码: <input type="password" name="password"/>
<button>登录</button>
</from>
)
}
ReactDOM.render(<Login/>,document.getElementById('test'))
}
</script>
在HTML中,表单元素的标签<input>、<textarea>、<select>
等的值改变通常是根据用户输入进行更新。
在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 进行更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为受控组件。
比如,给表单元素input绑定一个onChange事件,当input状态发生变化时就会触发onChange事件,从而更新组件的state。
受控组件更新state的流程
1、 可以通过初始state中设置表单的默认值
2、每当表单的值发生变化时,调用onChange事件处理器
3、事件处理器通过事件对象event拿到改变后的状态,并更新组件的state
4、一旦通过setState方法更新state,就会触发视图的重新渲染,完成表单组件的更新
React中数据是单项流动的,从示例中,可以看出表单的数据来源于组件的state,并通过props传入,这也称为单向数据绑定。然后又通过onChange事件处理器将新的数据写回到state,完成了双向数据绑定。
受控组件
受控组件依赖于状态
受控组件的修改会实时映射到状态值上,此时可以对输入的内容进行校验
受控组件只有继承React.Component才会有状态
受控组件必须要在表单上使用onChange事件来绑定对应的事件
非受控组件
非受控组件不受状态的控制
非受控组件获取数据就是相当于操作DOM
非受控组件可以很容易和第三方组件结合,更容易同时集成 React 和非 React 代码
两者使用场景
1、受控组件使用场景:一般用在需要动态设置其初始值的情况。例如:某些form表单信息编辑时,input表单元素需要初始显示服务器返回的某个值然后进行编辑。
2、非受控组件使用场景:一般用于无任何动态初始值信息的情况。例如:form表单创建信息时,input表单元素都没有初始值,需要用户输入的情况。
当一个函数符合下面两个规范中的任何一个,那该函数就是高阶函数
例如:Promise、setTimeout、arr.mup() 等等
函数柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式,刚刚的saveFormData其实也用到了函数的柯里化,那么我们来简单演示一下你就明白了
在开发中我们常常会遇到注册账号的需求。用户输入用户名、密码。
<script type="text/babel">
class Login extends React.Component{//创建组件
State={//初始化状态
username='',//用户名
password='',//密码
}
saveUsername=(exent)=>{//保存用户到状态中
this.setState({username:event.target.value})
}
savePassword=(exent)=>{
this.setState({password:event.target.value})
}
handleSubmit=(exent)=>{
event.preventDefault()//阻止表单提交
const{username,password}=this.state
alert{`你输入的用户名是${username},你输入的密码是:${password}`}
}
render(){
<from>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>注册</button>
</from>
}
}
</script>
这里可以看到saveUsername()和savePassword()这两条函数其实非常啰嗦,当需要保存的状态变多后不便于维护,如之后还要保存用户的身份证号、电话等信息。
【复习】对象的基本操作:在对象中想要拿到某个属性值名称需要使用 [ 属性名 ]
let a = 'name'
let obj={} //{name:obj}
obj[a]='tom'
我们使用高阶函数来重写编写刚刚的需求
<script type="text/babel">
class Login extends React.Component{//创建组件
state = {//初始化状态
username='',//用户名
password=''//密码
}
saveFormData=(dataType)=>{//保存表单数据到状态中
return (event)=>{
this.setState([dataType]:event.target.value)
}
}
handleSubmit = (exent)=>{//表单提交的回调
event.preventDefault()//阻止表单提交
const{username,password}=this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
<from>
用户名:<input onChange={this.saveFromData(username)} type="text" name="username"/>
密码:<input onChange={this.saveFromData(password)} type="password" name="password"/>
<button>注册</button>
</from>
}
}
</script>
所谓函数柯里化是通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式,刚刚的saveFormData其实也用到了函数的柯里化,形式如下
function sum(a){
return (b)=>{
return (c)=>{
return a+b+c;
}
}
}
const result = sum(1)(2)(3)
不使用柯里化的写法
<script type="text/babel">
//创建组件
class Login extends React.Component{
//初始化状态
state = {
username='',//用户名
password=''//密码
}
//保存表单数据到状态中
saveFormData=(dataType,value)=>{
this.setState([dataType]:value)
}
//表单提交的回调
handleSubmit = (exent)=>{
event.preventDefault()//阻止表单提交
const{username,password}=this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
<from>
用户名:<input onChange={event=>this.saveFromData('username',event)} type="text" name="username"/>
密码:<input onChange={event=>this.saveFromData('password',event)} type="password" name="password"/>
<button>注册</button>
</from>
}
}
</script>
手机扫一扫
移动阅读更方便
你可能感兴趣的文章