React 组件
1.React 特点
1.采用组件化模式,声明式编码,提高开发效率及组件复用率
2.在React Native中使用React语法进行移动端开发
3.使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互
注:React 高效的原因:使用虚拟DOM;Diffing算法:最小化页面重绘
2.Hello_React 小案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSX 练习</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javaScript" src="../../react/js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javaScript" src="../../react/js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javaScript" src="../../react/js/babel.min.js"></script>
<script type="text/babel"> // 此处一定要写bael
//1.创建虚拟DOM
const VDOM=(<div>新年好</div>); // 此处一定不要写引号,因为不是字符串
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>
</html>
3.虚拟DOM的两种创建方式
1.使用jsx创建虚拟DOM
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSX 练习</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javaScript" src="../../react/js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javaScript" src="../../react/js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javaScript" src="../../react/js/babel.min.js"></script>
<script type="text/babel"> // 此处一定要写bael
//1.创建虚拟DOM
const VDOM=(<div id="test"><span>新年好</span></div>); // 此处一定不要写引号,因为不是字符串
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>
</html>
2.使用js创建虚拟DOM(一般不用)
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>用jsx创建虚拟DOM</title>
</head>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javaScript" src="../../react/js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javaScript" src="../../react/js/react-dom.development.js"></script>
<script type="text/javaScript">
//1.创建虚拟DOM
// const VDOM=React.createElement(标签名,标签属性,标签内容);
const VDOM=React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React');
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>
</html>
*****中途想查看一下npm 镜像源,百度了一下:npm config get registry
3.比较VDOM与TDOM
console.log(VDOM); //Object
console.log(document.getElementById('test')) //<div id="test">...</div>
关于虚拟DOM:
(1)本质是Object类型的对象(一般对象)
(2)虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是react内部使用的,无需真实DOM那么多的属性
(3)虚拟DOM最终会被React转为真实的DOM,呈现在页面上
4.JSX语法规则
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>JSX 语法</title>
</head>
<style>
.title{
background-color: orange;
}
</style>
<body>
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javaScript" src="../../react/js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javaScript" src="../../react/js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javaScript" src="../../react/js/babel.min.js"></script>
<script type="text/babel"> // 此处一定要写bael
const idtest="lOvEmYSelf"
const content="愿你不负自己"
//1.创建虚拟DOM
const VDOM=(
<div>
<h1 className="title" id={idtest.toUpperCase()}>
<span style={{color:"white"}}>
{content.toLowerCase()}
</span>
</h1>
<input type="text" />
</div>
); // 此处一定不要写引号,因为不是字符串
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>
</html>
语法规则
(1)创建虚拟DOM时不能加引号
(2)标签中混入js表达式时要用{}
(3)样式的类名指定不要用class,要用className
(4)内联样式,要用style={{key:value}}的形式去xie
为什么是双括号:最外层是参考第二条,第二层是当使用键值对形式时需要{}
(5)只有一个根标签
(6)标签必须闭合
(7)标签首字母
. …若小写字母开头,则转为HTML中同名标签,若HTML中无该对应的标签,则报错
…若大写字母开头,则react就去渲染对应的组件,若组件没有定义,则报错
自述:前面敲代码时有几个问题:
1.忘记CSS最外层包裹标签为什么了:<style></style>
2.不记得js转字母转大写、小写的方法:toUpperCase(),toLowerCase()
5.JSX练习
<script type="text/babel"> // 此处一定要写bael
var arrays=['Angular','Vue','Reace']
//1.创建虚拟DOM
const VDOM=(
<div>
<h1>前端js框架列表</h1>
<ul>
arrays.map((items)=>{
return <li>{items}</li>
})
</ul>
</div>
);
//2.渲染虚拟DOM到页面
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
报错Uncaught SyntaxError: Inline Babel script: Unexpected token (9:19)
解决方案:
{
arrays.map(function name(items) {
return <li>{items}</li>
})
}
<ul></ul>
标签内部的map方法少最外层{}。违反了jsx语法规则2
报错:react.development.js:276 Warning: Each child in a list should have a unique "key" prop.
解决方案:为li标签设定key唯一值(但设定索引值会造成不可预估的错误,不推荐,先暂时解决)
<ul>
{
arrays.map((items,index)=>{
return <li key={index}>{items}</li>
})
}
</ul>
6.js语句(代码)与js表达式区别
区分【js语句(代码)与js表达式】
1、表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
下面这些都是表达式
(1).a // 变量
(2).a+b // 变量+变量
(3)demo(1) // 方法(传的参数)
(4)arr.map() // 数组遍历
(5)function test(){} // 直接在内部写方法
2、语句(代码)
下面这些都是语句(代码)
(1).if
(2).for(){}
(3)switch(){case:xxx}
4.模块,模块化,组件,组件化概念
模块:1个js为一个模块
组件:1个简单的html、css、js所构成的一个页面为一个组件(实现局部页面)
模块化:应用以js为模块来编写
组件化:多个组件构成的应用为组件化
5.React面向组件编程
1.下载react开发者调试工具:React Developer Tools
2.创建组件的两种方式:函数式与类式组件
2.1.函数式组件(适用于简单组件)
<script type="text/babel"> // 此处一定要写bael
//1.创建函数式组件
function Demo(){
console.log(this);// undefined 原因:babel编译后开启ES5严格模式:禁止自定义函数的this指向window
return <h1>要开心,要快乐,要努力!</h1>
}
//2.渲染虚拟DOM到页面
ReactDOM.render(<Demo/>,document.getElementById('test'));
/*
执行完 ReactDOM.render(<Demo/>....发生了什么
1.React解析组件标签,找到Demo组件
2.发现组件是函数定义的,调用该函数,将虚拟的DOM转为真实DOM,呈现页面
*/
</script>
2.2类式组件
2.2.1.类的基本知识
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>类的基本知识</title>
</head>
<body>
<script type="text/javaScript">
/*
总结:
1.类中的构造器不是必须写的,要对实例进行一些初始化操作,如添加指定属性时才写。
2.如果A类继承了B类,且A类中写了构造器,那么A类构造器中的super是必须调用的
3.类中所定义的方法,都是放在了类的原型对象上,供实例使用
*/
class demo{
// 构造器方法
constructor(name,age){
// 构造器的this是谁--类的实例对象
this.name=name;
this.age=age;
}
// 一般方法
speak(){
// speak 方法放在哪了?--类的原型对象上,供实例使用
//通过demo实例调用speak时,speak中的this就是demo实例
console.log( `${this.name}是${this.age}`);
}
}
//创建一个demo1继承于demo类
class demo1 extends demo{
constructor(name,age,content){
super(name,age);
this.content=content;
}
// 重写从父类继承过来的方法
speak(){
console.log( `${this.name}是${this.age}${this.content}`);
}
study(){
// speak 方法放在哪了?--类的原型对象上,供实例使用
//通过demo实例调用speak时,speak中的this就是demo实例
console.log("世人笑我太疯癫,我笑别人看不穿!")
}
}
const d1=new demo("葛朋飛","小垃圾");
const d2=new demo1("我","你","的誰");
d1.speak();
d2.speak();
d2.study();
</script>
</body>
</html>
2.2.2. 类的demo
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>类式组件</title>
</head>
<body>
<div id="test"></div>
<script type="text/javaScript" src="../../../react/js/react.development.js"></script>
<script type="text/javaScript" src="../../../react/js/react-dom.development.js"></script>
<script type="text/javaScript" src="../../../react/js/babel.min.js"></script>
<script type="text/babel">
// 1.创建类式组件
class MyComponent extends React.Component{
render(){
// render是放在哪里的?--MyComponent的原型对象上,供实例使用
// render中的this是谁?--MyComponent的实例对象<==>MyComponent组件实例对象
console.log("render中的this"+this);// MyComponent{props:{},context:{},refs:{},state:null...}
return <h1>适用于复杂组件(需要改变状态的组件为复杂组件)</h1>
}
}
//2.渲染组件到页面
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
/*
执行了 ReactDOM.render(<MyComponent/>...之后发生了什么?
1.React解析组件标签,找到了MyComponent组件
2.发现组件是通过类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面上
*/
</script>
</body>
</html>
2.2.3. 简单组件与复杂组件
简单组件:无State
复杂组件:无State
6.组件实例的三大核心属性
1.state-改变数值
1.1 原生事件绑定–三种
<body>
<button id="btn1">按钮1</button>
<button id="btn2">按钮2</button>
<button onclick="demo()">按钮3</button>
<script type="text/javaScript">
const btn1=document.getElementById('btn1');
btn1.addEventListener('click',()=>{
console.log("按钮1被点击了")
})
const btn2=document.getElementById('btn2');
btn2.onclick=()=>{
console.log("按钮2被点击了");
}
function demo(){
console.log("按钮3被点击了");
}
</script>
</body>
1.2 demo
今天天气很凉爽
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!-- 准备好一个容器 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script src="../js/react-development.js" type="text/javascript"></script>
<!-- 引入react DOM 用于支持react 操作DOM -->
<script src="../js/react-dom.development.js" type="text/javascript"></script>
<!-- 引入babel 用于将jsx转为js -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
<script type="text/babel">
class IsWeather extends React.Component{
//构造器调用几次:1次,初始化的时候
constructor(props){
super(props);
// console.log(this);
// 初始化状态
this.state={is:true}
this.isWeather=this.isWeather.bind(this);
// bind做两件事:(1)生成新的函數(2)改變this指向
}
// render调用几次1+n次:1是初始化那次,n是状态更新的次数
render(){
// console.log(this)
// return <h1 onClick={this.isWeather}>今天天气很{this.state.is==true?'炎热':'凉爽'}</h1>
// 或者 就不用写this.state
const {is}=this.state; // 读取状态
return <h1 onClick={this.isWeather}>今天天气很{is==true?'炎热':'凉爽'}</h1>
}
// isWheather 点几次调用几次
isWeather(){
// isWeather放在哪里?--StateClass的原型对象上,供实例使用
// 由于isWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以isWeather中的this为undefined
// console.log(this);
const is=this.state.is
// this.setState=({is:!is},()=>{
// console.log(this.state.is)
// })--这是我当时困惑的为什么this中出现setState,导致点击不起作用。
this.setState({is : !is }, () => {
console.log(this.state.is);
});
}
}
ReactDOM.render(<IsWeather/>,document.getElementById('test'));
/*自述:为什么isWeather中的this为undefined
因为当你写一个方法,
function demo(){},调用demo()时,函数内部是指向windows。
class demo1{
study(){
console.log(this);
console.log("不负韶华");
}
}
const d1=new demo1();
d1.study();//demo1 {} --实例调用
const d2=d1.study;
d2();//undefined--直接调用
*/
</script>
</body>
</html>
1.3state的简写方式
class IsWeather extends React.Component{
//构造器调用几次:1次,初始化的时候
// constructor(propos){
// super(propos);
//this.state={isHot:true};//在外头定义即可
// this.isWeather=this.isWeather.bind(this); //方法用箭头函数
// }
//1.初始化状态
state={isHot:true};
render(){
return <div onClick={this.isWeather}>今天天气很{this.state.isHot?'炎热':'凉爽'}</div>
}
//自定义方法
//name=()=>{}箭头函数没有this会自动选择上一层级的this,所以它会找到IsWeather的this
// 下面这方法是赋值语句+箭头函数的形式。
isWeather=()=>{
const isHot=this.state.isHot;
this.setState=({isHot:!isHot});
}
}
ReactDOM.render(<IsWeather/>,document.getElementById('test'));
** 注意:**
1.组件中render方法中的this为组件实例对象
2.组件中自定义方法中的this为undefined,如何解决
①.强制绑定this:通过函数对象的bind()
②.箭头函数 name=()=>{}
3.状态数据不能直接修改或者更新,必须借助this.setState=({isHot:!isHot})
2.props-组件之间传值
2.1.用法demo
<script type="text/babel">
class DemoProds extends React.Component{
render(){
console.log(this);
const{name}=this.props
return (
<ul>
<li>姓名:{name}</li>
</ul>
)
}
}
const p={name:'小幸运'}
//下面两种方法均可
// ReactDOM.render(<DemoProds name='小幸运'/>,document.getElementById('test'))
ReactDOM.render(<DemoProds {...p}/>,document.getElementById('test'))
</script>
2.2.展开运算符复习(可用于展开数组,连接数组)
<script type="text/javaScript">
let arr1=[1,2,3,4,5];
let arr2=[6,7,8,9,0];
console.log(...arr1);//展开一个数组 1 2 3 4 5
let arr3=[...arr1,...arr2];// 连接数组 [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
console.log(arr3)
// 在函数中使用
function sum(...numbers){
console.log(numbers);// [1, 2, 3, 4]
// 数组求和
return numbers.reduce((pre,nex)=>{
console.log(pre+"---"+nex)//1---2---3---4
return pre+nex;
})
}
console.log(sum(1,2,3,4));
// 构造字面量使用的展开语法
let person={name:'无话',age:18};
let person2={...person};
// console.log(...person);//报错,展开运算符不能展开对象 报错信息: Uncaught TypeError: Found non-callable @@iterator
person.name='jerry';
console.log(person);//{name: "jerry", age: 18}
console.log(person2);//{name: "无话", age: 18}
//合并
let person3={...person,name:'jack',age:'88'};
console.log(person3);//{name: "jack", age: "88"}
</script>
2.3 用例(设定参数类型、必要性的限制、默认值)
<script type="text/babel">
class DemoProds extends React.Component{
constructor(props){
//构造器是否接收props,是否传递给super,取决于:是否希望在构造器中通过this访问props
super(props);
console.log(this.props);
}
// 指定默认标签属性值
static defaultProps = {
sex:'男',//性别默认值为男
age:19//年龄默认值为19
}
// 对标签属性进行类型、必要性的限制
static propTypes= {
name : PropTypes.string.isRequired,//限制name为必传,且为字符串
age : PropTypes.number,//限制age为数字类型
sex:PropTypes.string,//限制sex为数字类型
speak:PropTypes.func//限制speak为函数类型
};
render(){
console.log(this);
const{name,age,sex}=this.props;
// props的值只能为只读
// this.props.name="jack";//此行代码会报错,因为props的值为只读
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
const p={name:'幸运',sex:'女',age:18};
//下面两种方法均可
ReactDOM.render(<DemoProds name='何其' sex=" 男" age={21} speak={speak}/>,document.getElementById('test1'))
ReactDOM.render(<DemoProds {...p}/>,document.getElementById('test'))
function speak(){
console.log("我说话了");
}
</script>
3.refs-双向绑定
3.1字符串形式的ref
<script type="text/babel">
// 创建组件
class Demo extends React.Component{
render(){
return (
<div>
<input type="text" ref="input1" placeholder="点击按钮提示数据"/>
<button type="button" onClick={this.showData}>点我提示左侧的数据</button>
<input type="text" onBlur={this.showData2} ref="input2" placeholder="失去焦点提示数据"/>
</div>
)
}
showData=()=>{
console.log(this);
const {input1} =this.refs;//<input type="text" placeholder="点击按钮提示数据">
console.log(input1.value)//输入的值
}
showData2=()=>{
console.log(this);
const {input2} =this.refs;//<input type="text" placeholder="点击按钮提示数据">
console.log(input2.value)//输入的值
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
3.2 回调函数形式的ref (常用)
ref={( c)=>{this.input1=c}}
<script type="text/babel">
// 创建组件
class Demo extends React.Component{
render(){
return (
<div>
<input type="text" ref={(c)=>{this.input1=c}} placeholder="点击按钮提示数据"/>
<button type="button" onClick={this.showData}>点我提示左侧的数据</button>
<input type="text" onBlur={this.showData2} ref={(c)=>{this.input2=c}} placeholder="失去焦点提示数据"/>
</div>
)
}
showData=()=>{
const {input1} =this;
console.log(input1.value)//输入的值
}
showData2=()=>{
const {input2} =this;
console.log(input2.value)//输入的值
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
3.3关于回调函数的一些问题
如果ref回调函数是以内联函数的方式定义的,在更新过程中会被调用两次,第一次传入参数为null,然后第二次会传入参数DOM元素。这是因为在每次渲染时会创建一个新的函数实例,所以Ref清空旧的ref,并设置新的,通过将ref的回调函数定义成class的绑定函数,可避免此类问题,但在大多数情况下他是无关紧要的。
<script type="text/babel">
// 创建组件
class Demo extends React.Component{
state={
isHot:true
}
render(){
const {isHot}=this.state;
return (
<div>
<span>今天天气很{isHot?'炎热':'凉爽'}</span>
<input type="text" ref={(c)=>{
this.input1=c;
console.log("@",this.input1)
}} placeholder="点击按钮提示数据"/>
<button type="button" onClick={this.showData}>点我提示左侧的数据</button>
<button type="button" onClick={this.changeWeather}>改变天气按钮</button>
</div>
)
}
showData=()=>{
const {input1} =this;
console.log(input1.value)//输入的值
}
changeWeather=()=>{
const {isHot}=this.state;
this.setState({isHot:!isHot})
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
注:当改变天气时(更新数据状态),ref的回调函数会执行两次:打印结果如下:
@ null
@ <input type="text" placeholder="点击按钮提示数据">
** 解决方案
<input type="text" ref={this.saveInput}/>
// render外写:
saveInput=(c)=>{
this.input1=c;
console.log("@",this.input1)
}
3.4createRef (推荐)
<script type="text/babel">
// 创建组件
class Demo extends React.Component{
/*
React.createRef调用后可以返回一个容器,
该容器可以存储被ref所标识的节点,该容器是专人专用
*/
myRef=React.createRef();
myRef2=React.createRef();
render(){
return (
<div>
<input type="text" ref={this.myRef}/>
<button type="button" onClick={this.showData}>点我提示左侧的数据</button>
<input type="text" onBlur={this.showData2} ref={this.myRef2} />
</div>
)
}
showData=()=>{
console.log(this.myRef.current.value)//输入的值
}
showData2=()=>{
console.log(this.myRef2.current.value)//输入的值
}
}
ReactDOM.render(<Demo />,document.getElementById('test'))
</script>
4.React中的事件处理
4.1 概念
(1).通过onXXX指定事件处理函数(注意大小写->react中用驼峰,如onClick)
a.React使用的是自定义(合成)事件,而不是使用的是原生DOM事件–为了更好的兼容性。
b.React中的事件是通过事件委托的方式处理的(委托给组件最外层的元素)–为了高效
(2).通过event.target得到发生事件的DOM元素对象—不要过度使用ref
当事件就在获取值的同一个标签中,可以去除ref,在事件传递参数即可
<input type="text" onBlur={this.showData2} />
showData2=(event)=>{
//event.target 获取的是<input type="text" onBlur={this.showData2} />
console.log(event.target.value)//输入的值
}
5 收集表单数据
5.1 非受控组件
通过ref接收原生dom,通过dom属性中的value值接收参数
原始form表单->
action:指定点击提交后跳转的路径;
onSumbit:执行button type="submit" 的按钮
event.preventDefault();:禁止action路径跳转
form表单提交时,执行action方法时跳转会携带表单中带name属性的值
<script type="text/babel">
class Login extends React.Component{
handelSubmit=(event)=>{
event.preventDefault(); // 取消页面刷新,阻止表单跳转至action指定路径
const {username,password} =this;
//用户名为[object HTMLInputElement],账户密码未233
alert(`用户名为${username},账户密码未${password.value}`)
}
render(){
return (
<form onSubmit={this.handelSubmit}>
<input type="text" name="username" ref={c=>this.username=c}/>
<input type="password" name="password" ref={c=>this.password=c}/>
<button type="submit">提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById("test"))
</script>
5.2 受控组件
使用state接收前台参数
<script type="text/babel">
class Login extends React.Component{
state={
username:""
}
handelSubmit=(event)=>{
event.preventDefault();
const {username} =this.state;
alert(`用户名为${username}`)
}
saveUserName=(event)=>{
console.log(event.target.value);
this.setState({username:event.target.value})
}
render(){
return (
<form onSubmit={this.handelSubmit}>
<input type="text" name="username" onChange={this.saveUserName}/>
<button type="submit">提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById("test"))
</script>
5.3勿过度使用refs,所以尽量使用受控组件
6.高阶函数_函数柯里化
6.1 使用高阶函数_函数柯里化
0.概念
(1).高阶函数:如果一个函数符合如下两个规范中的任何一个,那该函数就是高阶函数
1.1 、若A函数接收的参数是一个函数
1.2、若A函数调用的返回值依然是一个函数
常见高阶函数:Promise,arr.map(),setTimeOut等等
(2)函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
function sum(a){
return (b){
return ( c){
return a+b+c;
}
}
}
sum(a)(b)( c) // 调用
1.不同input框中的值使用同一个函数进行获取值
onChange={this.saveFormData('username')}
2.函数内部使用return 函数
其实onChange={this.saveFormData} 返回的是一个函数
onChange={this.saveFormData('username')} 返回undefined,所以需要 函数内部return 回调函数
3.dataType:event.target.value 是将值赋给dataType字符串
this.setState({[dataType]:event.target.value}) 是将值赋给dataType变量
<script type="text/babel">
class Login extends React.Component{
saveFormData=(dataType)=>{
return (event)=>{
console.log(dataType,event.target.value);
this.setState({[dataType]:event.target.value})
}
}
render(){
return (
<form onSubmit={this.handleSubmit}>
<input type="text" name="username" onChange={this.saveFormData('username')}/>
<input type="text" name="password" onChange={this.saveFormData('passowrd')}/>
<button type="submit">提交</button>
</form>
)
}
}
ReactDOM.render(<Login/>,document.getElementById("test"))
</script>
记住:必须将一个函数返回给onChange等事件
6.2 不使用高阶函数_函数柯里化
<input type="text" name="username" onChange={(event)={this.saveFormData('username',event)}/}/>
saveFormData=(dataType,event)=>{
this.setState({[dataType]:event.target.value})
}
7.生命周期函数
生命周期函数==》生命周期钩子函数==》生命周期回调函数==》生命周期钩子
7.1例子
1.隐藏组件:ReactDOM.unmountComponentAtNode
2.初始化后执行方法:componentDidMount
3.关闭组件之前:componentWillUnmount
<script type="text/babel">
class Login extends React.Component{
state={opacity:1}
death=()=>{
// 清除定时器 this.timer 为定时器id,说明是哪个定时器
clearInterval(this.timer);
// 卸载组件--点击button按钮使类式组件消失
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
}
// 组件挂载完毕后执行的方法,即执行完render
componentDidMount() {
this.timer =setInterval(()=>{
let {opacity} =this.state;
opacity-=0.1;
if(opacity<=0){
opacity=1;
}
this.setState({opacity})
},200)
}
// 组件将要被卸载
componentWillUnmount(){
// 在这里关闭定时器也可
}
// 初始化渲染,状态更新之后
render(){
return (
<div>
<h1 style={{opacity:this.state.opacity}}>学不会react,我怎么办</h1>
<button onClick={this.death}>不活了</button>
</div>
)
}
}
ReactDOM.render(<Login/>,document.getElementById("test"))
</script>
7.2 生命周期函数执行顺序
1.初始化时执行的顺序:constructor–>componentWillMount–>render–>componentDidMount–>componentWillUnmont
2.数值更新的顺序:setState–>shouldComponentUpdate–>componentWillUpdate–>render–>componentDidMount–>componentWillUnmont
3.数值强制更新:this.forceUpdate–>componentWillUpdate–>render–>componentDidMount–>componentWillUnmont
4.父子组件通信:componentWillReceiveProps–>shouldComponentUpdate–>componentWillUpdate–>render–>componentDidMount–>componentWillUnmont
组件名称 | 功能 | 使用频率 | 使用场景 |
---|---|---|---|
constructor | 构造器 | ||
componentWillMount | 组件生成之前执行的函数 | ||
render | 组件生成的函数 | 常用 | 初始化渲染调用或更新渲染调用 |
componentDidMount | 组件生成后执行的函数 | 常用 | 开启定时器,消息订阅,发送网络请求–开启监听,发送ajax请求 |
componentWillUnmont | 组件卸载前执行的函数 | ||
– | – | ||
this.setState | 数值更新执行函数 | ||
this.forceUpdate | 数值强制更新 | ||
shouldComponentUpdate | 组件是否进行更新函数,若不写,则默认为true,若写,则根据 return true or false 进行判断。 | ||
componentWillUpdate | 若shouldComponentUpdate 返回true ,则更新之前执行的函数 | ||
componentDidMount | 若shouldComponentUpdate 返回true ,则更新之后执行的函数 | ||
componentWillReceiveProps | 父子组件通信时,子组件接收父组件数据之前执行的函数(但第一次赋值是不执行的) | ||
ReactDOM.unmountComponentAtNode(document.getElementById(“test”))之前 执行componentWillUnmount | 卸载组件 | 常用 | 关闭定时器,取消消息订阅–做一些收尾动作 |
推荐使用BootCDN-前端核心JS库-下载JS
7.3 新的生命周期与旧的生命周期区别
新的生命周期与旧的生命周期并无多少区别,只在于:
1.废弃了三个钩子
componentWillMount–》UNSAFE_componentWillMount
componentWillUpdate–》UNSAFE_componentWillUpdate
componentWillReceiveProps–》UNSAFE_componentWillReceiveProps
React 官网提示,过时的组件生命周期往往会带来不安全的编码实践,所以以后可能会废弃
2.新增了两个钩子
getDeriveStateFromProps
(0)函数必须为静态,并且必须有返回值为null,或者为props对象{count:1}
(1)功能:将props里的值赋给state,
(2)使用场景:当state的值在任何时候都取决于props时
// 功能:将props里的值赋给state,
// 使用场景:当state的值在任何时候都取决于props时
static getDeriveStateFromProps(props,state){
return props;
}
ReactDOM.render(<Demo count="1"/>,document.getElementById("test"));
getSnapshotBeforeUpdate
使用场景如下:
<body>
<div id="test"></div>
<script src="../../react/js/17.0.2/react.development.js" type="text/javaScript"></script>
<script src="../../react/js/17.0.2/react-dom.development.js" type="text/javaScript"></script>
<!-- Cannot read property 'keys' of undefined babel 版本过高,还原以前版本-->
<script src="../../react/js/babel.min.js" type="text/javaScript"></script>
<script src="../../react/js/17.0.2/prop-types.js" type="text/javaScript"></script>
<style>
.list{
width:200px;
height: 150px;
background-color:green;
overflow: auto;
}
.news{
height:40px;
}
</style>
<script type="text/babel">
class List extends React.Component{
state={
arr:[]
}
// 组件生成后执行的钩子
componentDidMount(){
setInterval(() => {
const {arr}=this.state;
const newarr="新闻"+(arr.length+1);
this.setState({arr:[newarr,...arr]});
}, 1000);
}
// 记录滚动条内容长度
getSnapshotBeforeUpdate(){
return this.refs.list.scrollHeight;
}
// 数据更新后,累加;height是getSnapshotBeforeUpdate return 值
componentDidUpdate(preProps,preState,height){
this.refs.list.scrollTop+=this.refs.list.scrollHeight-height;
}
render(){
return (
<div className="list" ref="list">
{
this.state.arr.map((temp,index)=>{
return <div className="news" key={index}>{temp}</div>
})
}
</div>
)
}
}
ReactDOM.render(<List/>,document.getElementById("test"));
</script>
</body>
7.4 总结
8. Diffing 算法原理
8.1 原理与优势
原理:React的Diffing 原理是比较将旧的虚拟DOM与新的虚拟DOM做比较,比较粒度为标签,若旧的虚拟DOM与新的虚拟DOM相同,则使用旧的虚拟DOM。若不同则使用新的。
优势:大大提高页面映射效率。
8.2 React 中key值设定
1.使用索引 index 进行设定。
2.数据中添加id(唯一标记属性值)进行设定。—推荐使用
面试常考题如下:
9.路由
9.1 示例
注:
1.原生html中用 <a>
跳转不同页面
<a href="./home.html" ></a>
在React中靠路由连接实现切换组件–编写路由连接
<Link to="/home">home</Link>
React 中注册路由
<Route path="/home" component={Home}>home</Route>
9.2 <MyNavLink>
的封装与Switch的使用
1.MyNavLink的封装
<MyNavLink to="/home"></MyNavLink>
MyNavLink Component:
<Link {...this.props} />
2.Switch的使用
<Switch> // 功能是当匹配了某条route,则不再往下匹配
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
</Switch>
3.模糊查询与精准查询
模糊查询:
//是可以找到home组件的
<MyNavLink to="/home/a/b"></MyNavLink>
<Route path="/home" component={Home} />
//无法找到home组件
<MyNavLink to="/home"></MyNavLink>
<Route path="/home/a/b" component={Home} />
精准查询:添加exact --严格匹配优势会造成二级路由无法查询
<Route path="/home" exact component={Home} />
9.3 Redirect 的使用
1.一般编写在路由的最下方,当所有路由无法匹配时将会跳转到Redirect 指定路由
2.具体编码
<Switch>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/about" />
</Switch>
9.4二级路由的使用
App.js
<BrowserRouter>
<div style={{width:'25%',float:'left'}}>
<Link to="/home">home</Link>
<br/>
<Link to="/about">about</Link>
</div>
<div style={{width:'75%',float:'right'}}>
<Route path="/home" component={Home} />
<Route path="/about" component={About} />
<Redirect to="/home" />
</div>
</BrowserRouter>
Home.js
<div>
<Link to="/home/news">News</Link>
<Link to="/home/message">Message</Link>
<Route path="/home/news" component={News}></Route>
<Route path="/home/message" component={Message}></Route>
<Redirect to="/home/news" />
</div>
HOME/NEWS/index.jsx
<div>
<ul>
<li>君不见黄河之水天上来</li>
<li>奔流到海不复回</li>
</ul>
</div>
HOME/Message/index.jsx
<div>
<ul>
<li>君不见高堂明镜悲白发</li>
<li>朝如青丝暮成雪</li>
</ul>
</div>
9.5 向路由组件传递参数
9.5.1 params参数
路由:
// 路由传递参数:${msgobj.id}
<Link to={`/home/message/detail/${msgobj.id}/${msgobj.text}`}>{msgobj.text}</Link>
// 声明接收参数
<Route path="/home/message/detail/:id/:text" component={Detail} />
接收路由组件
const {id,text}=this.props.match.params;
/Home/Message
import React, { Component } from 'react';
import {Link,Route} from 'react-router-dom'
import Detail from './Detail';
class index extends Component {
state={
messageList:[
{id:1,text:"君不见高堂明镜悲白发"},
{id:2,text:"朝如青丝暮成雪"},
{id:3,text:"人生得意须尽欢"},
{id:4,text:"莫使金樽空对月"}
]
}
render() {
const {messageList}=this.state;
return (
<div>
<ul>
{
messageList.map((msgobj)=>{
return (
<li key={msgobj.id}>
{/* 向组件传递params参数 */}
<Link to={`/home/message/detail/${msgobj.id}/${msgobj.text}`}>{msgobj.text}</Link>
</li>
)
})
}
</ul>
<hr />
{/*声明接收Params参数 */}
<Route path="/home/message/detail/:id/:text" component={Detail} />
</div>
);
}
}
export default index;
/Home/Message/Detail
import React, { Component } from 'react';
const detailData=[
{id:1,content:"你好,地球"},
{id:2,content:"你好,月球"},
{id:3,content:"你好,火星"},
{id:4,content:"你好,中国"},
]
class index extends Component {
render() {
console.log(this.props);
const {id,text}=this.props.match.params;
// 获取路由传递参数id与detailData中id一致的内容
// 从路由传过来的id为字符串
const findResult=detailData.find((detailObj)=>{
return detailObj.id.toString()===id;
})
return (
<div>
<ul>
<li>ID:{id}</li>
<li>TITLE:将进酒</li>
<li>CONTENT:{text}</li>
<li>WELCOME:{findResult.content}</li>
</ul>
</div>
);
}
}
export default index;
9.5.2 search参数
路由组件:
<Link to={`/home/message/detail?id=${msgobj.id}&text=${msgobj.text}`}>{msgobj.text}</Link>
<Route path="/home/message/detail" component={Detail} />
接收路由参数:
import qs from 'querystring'
const {id,text}=qs.parse(this.props.location.search.slice(1))
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
知识点:qs.parse(?id=1&text=中国)==={id:1,text:中国}
9.5.3 state参数
路由组件:
<Link to={{pathname:"/home/message/detail",state:{id:msgobj.id,text:msgobj.text}}}>{msgobj.text}</Link>
<Route path="/home/message/detail" component={Detail} />
接收路由参数:
const {id,text}=this.props.location.state||{};
// 从路由传过来的id为字符串
const findResult=detailData.find((detailObj)=>{
return detailObj.id.toString()===id;
})||{}
备注:刷新也可以保留住参数
9.5.4 总结
9.5.6 路由的push与replace
push:进行路由的压栈操作(默认)
replace:路由替换操作,使history时返回不进行替换路由的返回
<Link replace to="/home/message">Message</Link>
9.5.7 编程式路由跳转 --通过点击按钮/图片等进行跳转
利用props中history.push方法和replace方法进行
参数携带方式可使用params,search,state方式接收
this.props.history
push:路由压栈跳转
replace:路由替换跳转
go:前进或后退几个路由,go(0)刷新
goForward:前进
goBack:回退
pushRouter=(id,title)=>{
this.props.history.push(`/home/message/detail/${id}/${title}`);
}
replaceRouter=(id,title)=>{
this.props.history.replace(`/home/message/detail/${id}/${title}`);
}
{
messageList.map((msgobj)=>{
return (
<li key={msgobj.id}>
<Link to={{pathname:"/home/message/detail",state:{id:msgobj.id,text:msgobj.text}}}>{msgobj.text}</Link>
<button onClick={()=>{this.pushRouter(msgobj.id,msgobj.text)}}>push路由參數</button>
<button onClick={()=>{this.replaceRouter(msgobj.id,msgobj.text)}}>replace路由參數</button>
</li>
)
})
}
9.6 向一般组件传递参数-withRouter
用法:
原本一般组件的props={}
但将组件withRouter加工过后就会有路由组件的参数
import React, { Component } from 'react';
import {withRouter} from 'react-router-dom'
class index extends Component {
render() {
console.log(this.props)
return (
<div>
<h1>React Demo</h1>
</div>
);
}
}
export default withRouter(index);
9.7 BrowserRouter与HashRouter的区别
1.底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值
2.url表现形式不一样
BrowserRouter路径中没有#,例:http://localhost:8000/test/demo
HashRouter路径中有#,例:http://localhost:8000/#/test/demo
3.刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保留在history对象中
HashRouter刷新后会导致路由state参数的丢失
4.HashRouter可以用于解决一些路径错误相关的问题
10.antd
10.1antd的按需引入+自定义主题
官网中有方法,左侧—在create-react-app中使用
11.Redux
定义:是一个专门用来做状态管理的JS库(不是react插件库)
作用:集中式管理react应用中多个组件共享的状态
使用情况:
(1)某个组件的状态,需要其他组件随时拿到----共享
(2)一个组件需要改变另一个组件的状态----通信
(3)总体原则:能不用就不用,如果通信非常费劲才要使用
11.1 redux的三个核心概念
action
1.动作的对象
2.包含两个属性
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值任意类型,可选属性
reducer
1.用于初始化状态,加工状态
2.加工时根据旧的state和action,产生新的state的纯函数
store
1.将state,action,reducer连接在一起的对象
2.如何得到此对象
import {createStore} from ‘redux’
import {reducer} from ‘./reducers’11.2 redux 的用法
1.yarn add redux
2.示例-- 精简版(无action creator):
store.js
/*
該文件專門用來暴露一個store對象,整個應用只有一個store對象
*/
// 引入createStore, 專門用於創建redux中最為核心的store對象
import { createStore } from 'redux'
//引入為Count組件服務的reducer --不帶{}為默認暴露,說明那個文件里只有一個export
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer);
count_reducer
/*
1.該文件是用於創建一個Count組件服務的reducer,reducer 本質就是一個函數
2.reducer 函數可以接收兩個參數,分別為:之前的狀態(preState),動作對象(action)
*/
// 初始化時preState=undefined action.type=@@Init 無action.data
const initState = 0; // 初始化狀態
export default function countReducer(preState=initState,action) {
// if(preState==undefined) preState = 0;
// 從action 對象中獲取:type,data
const {type,data}=action;
// 根據type決定如何加工數據
switch (type) {
case 'increment': // 如果是加
return preState+data
case 'decrement': // 如果是減
return preState+data
default:
return preState
break;
}
}
Count
import React, { Component } from 'react';
// 引入store
import store from '../../redux/store.js'
class Count extends Component {
state={count:0}
componentDidMount(){
// 監測redux中狀態的變化,只要變化就調用render
store.subscribe(()=>{
this.setState({});
})
}
// 加法
increment=()=>{
const {value} =this.selectNumber;
store.dispatch({type:'increment',data:value*1})
// const {count} =this.state;
// this.setState({count:count + value*1})
}
// 减法
decrement=()=>{
const {value} =this.selectNumber;
store.dispatch({type:'decrement',data:value*1})
// const {count} =this.state;
// this.setState({count:count - value*1})
}
// 奇数加
incrementIfOdd=()=>{
const {value} =this.selectNumber;
// const {count} =this.state;
const count =store.getState();
if(count % 2 !== 0){
store.dispatch({type:'increment',data:value*1})
// this.setState({count:count + value*1})
}
}
// 异步加
incrementAsync=()=>{
const {value} =this.selectNumber;
// const {count} =this.state;
setTimeout(() => {
// this.setState({count:count+value*1})
store.dispatch({type:'increment',data:value*1})
}, 500);
}
render() {
// const {count} =this.state;
return (
<div>
{/* <h1>当前求和为{count}</h1> */}
<h1>{store.getState()}</h1>
<select ref={c=>this.selectNumber =c}>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.increment}>+</button>
<button onClick={this.decrement}>-</button>
<button onClick={this.incrementIfOdd}>当前求和为奇数再加</button>
<button onClick={this.incrementAsync}>异步加</button>
</div>
);
}
}
export default Count;
- 全局监听组件,一旦改变值,则重新渲染render
index.js
store.subscribe(()=>{
ReactDOM.render(<App />,document.getElementById('root'))
})