React:地址------个人感觉 ,是最好的React教程之一,对新手友好。
1.React_hello
- index.html
<!-- 准备好一个容器 -->
<div id="container"></div>
<!-- 引入react核心库 -->
<script src="./js/react.development.js" crossorigin></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script src="./js/react-dom.development.js" crossorigin></script>
<!-- 引入babel,用于将jsx转为js -->
<script src="./js/babel.min.js"></script>
<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script src="https://cdn.bootcdn.net/ajax/libs/prop-types/15.7.2/prop-types.js"></script>
<!-- 此处一定要写type="text/babel" 在这里编写js的逻辑 -->
<script type="text/babel" src="./src/index.jsx"></script>
- index.jsx
2.虚拟DOM的两种创建方式及优缺点
2.1虚拟DOM的两种创建方式
- 虚拟DOM的创建方式一
- 虚拟DOM的创建方式二
2.2两种方式的优缺点
方式一
的创建DOM特点:结构简单/清晰明了可以使用{}
读取变量,但是只能写在.jsx
文件中方式二
的创建DOM特点:结构复杂无法使用{}
读取变量,可以写在.js/.jsx
文件中- 总结:直接使用方法一,方法二过于复杂
3.虚拟DOM和真实DOM的对比
- 代码
- 显示
- 总结
/**
* 关于虚拟DOM
* 本质就是Object类型的对象(一般对象)
* 虚拟DOM比较‘轻’,真实DOM比较‘重’,因为虚拟DOM是React内部再用,无需真实DOM上那么多的属性
* 虚拟DOM最终会被React转换成真实DOM,呈现在页面上
*/
4.jsx
的语法规则
/**
* jsx语法规则
* 1.定义虚拟DOM时,不要写引号
* 2.样式中混入js表达式的时候要用{}
* 3.样式中类名指定不要用class,要用className
* 4.内联样式,要用style={
{key:value,key1:value1,...}}
* 5.虚拟DOM必须只有一个跟标签
* 6.标签必须闭合
* 7.标签首字母
* 7.1小写字母开头,则该标签转为HTML同名元素,若HTML中无该标签对应的同名元素则报错
* 7.2大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错
* 8.{}中能自动遍历由简单的数据类型构成的数组
*/
/**
* 一定要注意区分:【js语句(代码)】与【js表达式】
* 1.一个表达式会产生一个值,可以放在任何一个需要值的地方==>a/a+b/a===b/demo(1)/arr.map()/function test(){}
* 2.语句(代码):下面这些都是语句/代码 if(){}/for(){}/switch(){case:xxx}
*/
5.组件/组件化
5.1组件
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image/video等)
- 作用:复用编码,简化项目编码,提高运行效率
5.2组件化
- 当应用是以多组件的方式实现,这个应用就是一个组件化的应用
6.函数式组件/类式组件
6.1函数式组件
function FuncMyComponent() {
console.log(this);//此处的this是undefined,因为babel编译开启了严格模式
return (
<div className="wrap">
<span>函数式组件</span>
</div>
)
}
/**
* 注意点:函数式组件 首字母必须大写/必须有返回值
* 执行了ReactDOM.render(<FuncMyComponent />...之后发生了什么?
* 1.react解析了组件标签,找到了FuncMyComponent组件
* 2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中
*/
ReactDOM.render(<FuncMyComponent />, document.querySelector('#container'));
6.2类式组件
/**
* 用类定义组件
* 1.类继承React.Component
* 2.类名的首字母大写
* 3.ReactDOM.render(<ClassMyComponent />...
* 3.1React解析组件标签,找到了ClassMyComponent组件
* 3.2发现组件是使用类定义的,随后new出来该类的实例并通过该实例调用原型上的render方法,将render返回的虚拟DOM转成真实DOM,随后呈现在页面中
*/
class MyComponent extends React.Component {
render() {
// render是放在哪里的?------类的原型对象上,供类的实例对象使用
// render中的this是谁?------类的实例对象/组件实例对象
return (
<div className="wrap">
<span>欢迎使用react</span>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#container'))
7.组件实例的三大核心属性-state
7.1初始化state
class MyComponent extends React.Component {
constructor(props) {
// 构造器中的this就是MyComponent的实例对象
super(props);
this.state = {
//初始化state state必须是一个对象
msg: '欢迎使用react',
bool: true
};
}
render() {
let {
bool } = this.state;
let wather = null;
if (bool) {
wather = '凉爽';
} else {
wather = '炎热';
}
return (
<div className="wrap">
<span>欢迎使用react</span>
<hr />
<span>今天天气很{
wather}</span>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#container'))
7.2React
中的事件
class MyComponent extends React.Component {
//构造器调用几次?------一次
constructor(props) {
// 构造器中的this就是MyComponent的实例对象
super(props);
this.state = {
msg: '欢迎使用react',
bool: true
};
//this.changeBool.bind(this)==>找到MyComponent原型上的changeBool方法,通过bind的方式将this指向MyComponent的实例对象,并挂载到MyComponent类的自身上
this.changeBool = this.changeBool.bind(this);
}
//changeBool调用几次? 触发几次调用几次
changeBool() {
// changeBool放在哪里? ------ MyComponent的原型对象上,供实例使用
// 由于changeBool是作为onClick的回调,所以不是通过实例调用的,是直接调用,又因为类中的方法默认开启了局部的严格模式,所以 changeBool中的this为undefined
let {
bool } = this.state;
// 警告:状态(state)不可以直接更改,需要借助一个内置的API去更改==>setState
// bool = !bool;//这是错误的写法
this.setState({
//正确写法 这里面的更新是替换是一种合并,而非替换
bool: !bool
})
}
//render调用几次?1+n次 n就是状态更新的次数
render() {
//在render中使用if条件语句
let {
bool } = this.state;
let wather = null;
if (bool) {
wather = '凉爽';
} else {
wather = '炎热';
}
return (
<div className="wrap">
<span>欢迎使用react</span>
<hr />
<span onClick={
this.changeBool}>今天天气很{
wather}</span>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#container'))
7.3state
的简写方式
class MyComponent extends React.Component {
state = {
//给类的自身添加state属性
msg: '欢迎使用react',
bool: true
}
changeBool = () => {
//给类的自身添加方法 ==>赋值语句+箭头函数
let {
bool } = this.state;
this.setState({
bool: !bool
})
}
render() {
console.log(this);
let {
bool } = this.state;
let wather = null;
if (bool) {
wather = '凉爽';
} else {
wather = '炎热';
}
return (
<div className="wrap">
<span>欢迎使用react</span>
<hr />
<span onClick={
this.changeBool}>今天天气很{
wather}</span>
</div>
)
}
}
ReactDOM.render(<MyComponent />, document.querySelector('#container'))
8.组件实例的三大核心属性-props
8.1props
的基本使用
class FatherCom extends React.Component {
state = {
msg: '这里是父亲组件'
}
changePropsVal = () => {
let {
key0, key1, key2, str, bool, num, obj, arr1, arr, arrObj, obj1 } = this.props;
console.log(str);
// 注意点 props的值是只读属性,不能修改
this.props.str = '修改str';//这里就会报错,
}
render() {
console.log(this.props);
let {
key0, key1, key2, str, bool, num, obj, arr1, arr, arrObj, obj1 } = this.props;
console.log(key0, key1, key2, str, bool, num, obj, arr1, arr, arrObj, obj1);
let {
msg } = this.state;
return (
<div className="father">
{
msg}
<hr />
<button onClick={
this.changePropsVal}>修改props的值</button>
</div>
)
}
}
let obj = {
'key0': '对象',
'key1': '对象1',
'key2': '对象2'
};
let arr = [1, 2, 3];
let arrObj = [
{
'key': '对象1' },
{
'key': '对象2' },
{
'key': '对象3' }
];
ReactDOM.render(<FatherCom {
...obj} str='字符串' bool={
true} num={
996} obj={
obj} arr1={
[1, 1, 1]} arr={
arr} arrObj={
arrObj} obj1={
{
age: 26 }} />, document.querySelector('#container'));
8.2对props
进行限制(类型/必要性/默认值)
//文档地址:https://react.docschina.org/docs/typechecking-with-proptypes.html
class FatherCom extends React.Component {
state = {
msg: '这里是父亲组件'
}
static propTypes = {
//限制标签属性类型以及是否必填
name: PropTypes.string.isRequired,//姓名的类型为字符串并且必填
age: PropTypes.number//年龄的类型为数字非必填
}
static defaultProps = {
//给标签属性设置默认的值
sex: '男'//性别的默认值为男
}
render() {
console.log(this.props);
let {
name, age, sex, key0, key1, key2, str, bool, num, obj, arr1, arr, arrObj, obj1 } = this.props;
console.log(name, age, sex, key0, key1, key2, str, bool, num, obj, arr1, arr, arrObj, obj1);
let {
msg } = this.state;
return (
<div className="father">
{
msg}
<hr />
</div>
)
}
}
let obj = {
'key0': '对象',
'key1': '对象1',
'key2': '对象2'
};
let arr = [1, 2, 3];
let arrObj = [
{
'key': '对象1' },
{
'key': '对象2' },
{
'key': '对象3' }
];
ReactDOM.render(<FatherCom {
...obj} name='张三' age={
18} sex='女' str=' 字符串' bool={
true} num={
996} obj={
obj} arr1={
[1, 1, 1]} arr={
arr} arrObj={
arrObj} obj1={
{
age: 26 }} />, documen
8.3类的构造器(constructor)
和props
class FatherCom extends React.Component {
constructor(props) {
//接了props传给super this.props==>正常
// 构造器是否接受props,是否传递给super,取决于是否希望在构造器中通过this访问props
console.log(props);//{key0: "对象", key1: "对象1", key2: "对象2"}
super(props);
console.log('this.props', this.props);//{key0: "对象", key1: "对象1", key2: "对象2"}
}
// constructor() {//不接props不传给super this.props==>undefined
// super();
// console.log('this.props', this.props);//undefined
// }
state = {
msg: '这里是父亲组件'
}
render() {
console.log(this.props);
let {
key0, key1, key2 } = this.props;
console.log(key0, key1, key2);
let {
msg } = this.state;
return (
<div className="father">
{
msg}
<hr />
</div>
)
}
}
let obj = {
'key0': '对象',
'key1': '对象1',
'key2': '对象2'
};
ReactDOM.render(<FatherCom {
...obj} />, document.querySelector('#container'));
8.3函数组件使用props
function FatherCom(props) {
let {
key0, key1, key2, name, age, sex } = props;
return (
<ul>
<li>{
key0}</li>
<li>{
key1}</li>
<li>{
key2}</li>
<li>{
name}</li>
<li>{
age}</li>
<li>{
sex}</li>
</ul>
)
}
FatherCom.propTypes = {
//限制标签属性类型以及是否必填
name: PropTypes.string.isRequired,
age: PropTypes.number
}
FatherCom.defaultProps = {
//给标签属性设置默认的值
sex: '女'
}
let obj = {
'key0': '对象',
'key1': '对象1',
'key2': '对象2'
};
ReactDOM.render(<FatherCom name='张三' age={
18} sex='男' {
...obj} />, document.querySelector('#container'));
9.组件实例的三大核心属性-refs
9.1refs
-字符串形式(不推荐:会有效率上的问题)
class ClassRefsCom extends React.Component {
state = {
msg: '换用使用react'
}
tipInputValue = (event) => {
console.log(event, this.refs);
let {
firstInput } = this.refs;
console.log(firstInput.value);
}
getBlurInput = (event) => {
console.log(event.target.value);
}
render() {
let {
msg } = this.state;
return (
<div className="refs">
{
msg}
<hr />
<input ref='firstInput' type="text" placeholder="点击按钮提示数据" />
<button onClick={
this.tipInputValue}>提示输入数据</button>
<input ref='secondInput' type="text" onBlur={
this.getBlurInput} placeholder="失去焦点提示数据" />
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
9.2refs-回调函数形式(推荐)
class ClassRefsCom extends React.Component {
state = {
msg: '换用使用react',
bool: true
}
changeWeather = () => {
let {
msg, bool } = this.state;
this.setState({
bool: !bool
})
}
tipInputValue = (event) => {
//按钮触发事件-内联绑定方式-推荐
console.log(event, this);
let {
firstInput, secondInput } = this;
console.log(firstInput, secondInput, firstInput.value);
}
getBlurInput = (event) => {
//输入框失去焦点事件
console.log(event.target.value);
}
saveInput = (currentNode) => {
//ref-绑定函数方式
console.log(currentNode);
this.threeInput = currentNode;
console.log(this);
}
render() {
let {
msg, bool } = this.state;
let weather = null;
if (bool) {
weather = '冷'
} else {
weather = '热'
}
return (
<div className="refs">
{
msg}
<hr />
{
/* 绑定的第一种方式-内联绑定方式 */}
<input ref={
(currentNode) => {
//这个函数会自动执行,将[currentNode]挂载到this的实例上 随着render的每一次刷新,内联函数都是一个新的函数,这个都会重新执行,第一次为null(清空之前的) 第二次才是真的的dom节点
this.firstInput = currentNode;
}
} type="text" placeholder="点击按钮提示数据" />
<button onClick={
this.tipInputValue}>提示输入数据</button>
<input ref={
(currentNode) => {
this.secondInput = currentNode;
console.log(currentNode);
}
} type="text" onBlur={
this.getBlurInput} placeholder="失去焦点提示数据" />
{
/* 绑定的第二种方式绑定函数方式 随着render的刷新,不会调用两次,所以不会出现null的情况 */}
<input ref={
this.saveInput} type="text" />
<span onClick={
this.changeWeather}>今天{
weather}</span>
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
9.3refs-createRef
形式(推荐)
class ClassRefsCom extends React.Component {
state = {
msg: '换用使用react'
}
firstRef = React.createRef()
secondRef = React.createRef()
getInputVal = () => {
let {
firstRef, secondRef } = this;
console.log(firstRef, secondRef);
console.log(firstRef.current.value, secondRef.current.value);
}
render() {
let {
msg } = this.state;
return (
<div className="refs">
{
msg}
<hr />
<input ref={
this.firstRef} type="text" />
<input ref={
this.secondRef} type="text" />
<button onClick={
this.getInputVal}>获取输入框的数据</button>
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
10.React
中的事件处理
/**
* 1.通过onXxx属性指定事件处理函数,注意大小写
* 1.1.React使用的是自定义(合成)事件,而不是使用原生的DOM事件------为了更好的兼容性
* 1.2.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)------为了高效
* 2.通过event.target得到发生事件DOM元素对象------不要过度的使用ref
*/
class ClassRefsCom extends React.Component {
state = {
msg: '换用使用react'
}
eventTarget = (event) => {
console.log(event.target);
}
render() {
let {
msg } = this.state;
return (
<div className="refs">
{
msg}
<button onClick={
this.eventTarget}>react中的事件处理</button>
<hr />
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
10.1React
中的非受控组件(不推荐-不要过度的使用ref)
/**
* 非受控组件:页面中所有输入类的DOM都是现用现取
*/
class ClassRefsCom extends React.Component {
state = {
}
login = (event) => {
console.log(event.target);
event.preventDefault();
let {
userName, userpass } = this;
console.log(userName.value, userpass.value);
}
render() {
return (
<div className="refs">
<form action="">
用户名: <input ref={
(currentNode) => {
this.userName = currentNode;
}} type="text" />
密码: <input ref={
(currentNode) => {
this.userpass = currentNode;
}} type="password" />
<button onClick={
this.login}>登录</button>
</form>
<hr />
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
10.2React
中的受控组件(推荐)
/**
* 受控组件:页面中所有输入类的组件,可以随着输入的同时维护到状态中,就是受控组件
*/
class ClassRefsCom extends React.Component {
state = {
username: '',
userpass: ''
}
changeUserName = (event) => {
let {
target } = event;
this.setState({
username: target.value
})
}
changeUserPass = () => {
let {
target } = event;
this.setState({
userpass: target.value
})
}
login = (event) => {
console.log(event.target);
event.preventDefault();
let {
username, userpass } = this.state;
console.log(username, userpass);
}
render() {
return (
<div className="refs">
<form action="">
用户名: <input onChange={
this.changeUserName} type="text" />
密码: <input onChange={
this.changeUserPass} type="password" />
<button onClick={
this.login}>登录</button>
</form>
<hr />
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
10.3React
中的事件传参
10.3.1使用函数柯里化解决函数传参问题
/**
* 高阶函数:如果一个函数符合下面两个规范中的任何一个,那么该函数就是高阶函数
* 1.若A函数,接受的参数是一个函数,那么A就可以称之为高阶函数
* 2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
* 3.常见的高阶函数:Promise/setTimeout/arr/map等
*
* 函数柯里化:通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式
*/
class ClassRefsCom extends React.Component {
state = {
username: '',
userpass: ''
}
changeInputValue = (inputType) => {
//函数柯里化解决函数传参的问题
return (event) => {
let {
target } = event;
this.setState({
[inputType]: target.value
})
}
}
login = (event) => {
console.log(event.target);
event.preventDefault();
let {
username, userpass } = this.state;
console.log(username, userpass);
}
render() {
return (
<div className="refs">
{
/* 方法一:使用柯里化函数 */}
<form action="">
{
/* this.changeInputValue('username')的意思就是将this.changeInputValue('username')函数的返回值交给onChange去调用,所以我可以使用函数柯里化在this.changeInputValue种返回一个新的函数交给onChange去调用 */}
用户名: <input onChange={
this.changeInputValue('username')} type="text" />
密码: <input onChange={
this.changeInputValue('userpass')} type="password" />
<button onClick={
this.login}>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
10.3.2不使用函数柯里化解决函数传参问题
class ClassRefsCom extends React.Component {
state = {
username: '',
userpass: ''
}
changeInputValue = (inputType, event) => {
let {
target } = event;
this.setState({
[inputType]: target.value
})
}
login = (event) => {
console.log(event.target);
event.preventDefault();
let {
username, userpass } = this.state;
console.log(username, userpass);
}
render() {
return (
<div className="refs">
{
/* 方法二:直接返回一个函数 */}
<form action="">
用户名: <input onChange={
(event) => {
this.changeInputValue('username', event)
}} type="text" />
密码: <input onChange={
() => {
this.changeInputValue('userpass', event)
}} type="password" />
<button onClick={
this.login}>登录</button>
</form>
</div>
)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
11.React
的生命周期
- 生命周期图
11.1React
的生命周期-实例
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
opacity: 1
}
changeOpacity = () => {
//改变透明度事件 调用次数==>事件触发
let {
opacity } = this.state;
this.timer = setInterval(() => {
opacity -= 0.1;
if (opacity <= 0) {
opacity = 1;
}
this.setState({
opacity: opacity
})
}, 100);
}
unmountCom = () => {
//卸载组件事件 调用次数==>事件触发
ReactDOM.unmountComponentAtNode(document.querySelector('#container'))
}
render() {
//组件初始化/组件更新 调用次数==>1+n
let {
msg, opacity } = this.state;
return (
<div className="refs" style={
{
opacity: opacity }}>
<span>{
msg}</span>
<button onClick={
this.unmountCom}>卸载组件</button>
</div>
)
}
componentDidMount() {
//组件挂载完毕 调用次数==>1
console.log(this);
this.changeOpacity();
}
componentWillUnmount() {
//组件将要被卸载 调用次数==>1
clearInterval(this.timer)
}
}
ReactDOM.render(<ClassRefsCom />, document.querySelector('#container'))
11.2React
的生命周期-创建时
class ClassRefsCom extends React.Component {
constructor(props) {
super(props);
console.log('constructor');
}
state = {
msg: '欢迎实用react',
count: 0
}
changeState = () => {
let {
count } = this.state;
this.setState({
count: count += 1,
msg: 'changeMsg'
})
}
/**
* 此方法运用于一个罕见的用例:state在任何情况下都取决于props
* 无论是初始化还是修改,都不起作用
*/
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps-获取派生状态从props中', props, state);
// return null;
return props;
}
render() {
console.log('组件初始化/更新完毕', 'render');
let {
msg, count } = this.state;
return (
<div className="refs" >
<span>{
msg}-{
count}</span>
</div>
)
}
componentDidMount(str) {
//组件挂载完毕
console.log('组件挂载完毕', 'componentDidMount');
setTimeout(() => {
this.changeState();
}, 1000);
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
11.3React
的生命周期-更新时
11.3.1React
的生命周期-更新时-正常更新
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
count: 0
}
changeState = () => {
let {
count } = this.state;
this.setState({
count: count += 1
})
}
/**
* 此方法运用于一个罕见的用例:state在任何情况下都取决于props
* 无论是初始化还是修改,都不起作用
*/
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps-获取派生状态从props中', props, state);
return null;
// return props;
}
shouldComponentUpdate(props, state) {
//是否应该更新组件 返回布尔值==>默认为true
console.log('shouldComponentUpdate-是否应该更新组件', props, state);
return true;//继续走下一个生命周期
return false;//中止更新
}
render() {
console.log('组件初始化/更新完毕', 'render');
let {
msg, count } = this.state;
return (
<div className="refs" >
<span>{
msg}-{
count}</span>
<button onClick={
this.changeState}>点击更新state</button>
</div>
)
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate-获取快照在更新之前', prevProps, prevState);
return {
name: '张三',
age: 26
}
}
componentDidUpdate(prevProps, prevState, snapshotValue) {
console.log('componentDidUpdate-组件更新完毕', prevProps, prevState, snapshotValue);
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
11.3.2React
的生命周期-更新时-强制更新
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
count: 0
}
strongUpdate = () => {
this.forceUpdate();
}
/**
* 此方法运用于一个罕见的用例:state在任何情况下都取决于props
* 无论是初始化还是修改,都不起作用
*/
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps-获取派生状态从props中', props, state);
return null;
// return props;
}
render() {
console.log('组件初始化/更新完毕', 'render');
let {
msg, count } = this.state;
return (
<div className="refs" >
<span>{
msg}-{
count}</span>
<button onClick={
this.strongUpdate}>强制更新</button>
</div>
)
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log('getSnapshotBeforeUpdate-获取快照在更新之前', prevProps, prevState);
return {
name: '张三',
age: 26
}
}
componentDidUpdate(prevProps, prevState, snapshotValue) {
console.log('componentDidUpdate-组件更新完毕', prevProps, prevState, snapshotValue);
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
11.4React
的生命周期-卸载时
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
count: 0
}
unmountCom = () => {
ReactDOM.unmountComponentAtNode(document.querySelector('#container'))
}
render() {
console.log('组件初始化/更新完毕', 'render');
let {
msg, count } = this.state;
return (
<div className="refs" >
<span>{
msg}-{
count}</span>
<button onClick={
this.unmountCom}>卸载组件</button>
</div>
)
}
componentWillUnmount() {
//组件将要被卸载 调用次数==>1
console.log('componentWillUnmount------组件将要被卸载');
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
11.5React
的生命周期-getSnapshotBeforeUpdate/componentDidUpdate
使用实例
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
arr: []
}
stateArrPushItem = () => {
let {
arr } = this.state;
arr.push({
name: '新加' + (arr.length + 1),
id: arr.length
});
this.setState({
arr: arr
})
}
shouldComponentUpdate() {
return true;
}
render() {
console.log('组件初始化/更新完毕', 'render');
let {
arr } = this.state;
return (
<div className="wrap" ref={
(currentNode) => {
this.wrap = currentNode;
}}>
<ul>
{
arr.map(item => {
return (
<li key={
item.id}>{
item.name}</li>
)
})
}
</ul>
</div>
)
}
getSnapshotBeforeUpdate() {
let beforeUpdateWrapHeight = this.wrap.scrollHeight;
return {
beforeUpdateWrapHeight: beforeUpdateWrapHeight
}
}
componentDidUpdate(prevProps, prevState, snapshotValue) {
console.log(this.wrap.scrollHeight - snapshotValue.beforeUpdateWrapHeight);
this.wrap.scrollTop += this.wrap.scrollHeight - snapshotValue.beforeUpdateWrapHeight;
}
componentDidMount() {
setInterval(() => {
this.stateArrPushItem()
}, 500);
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
12.DOM
的diff
算法
/**
* 1.react/vue中key有什么作用?(key的内部原理是什么?)
* 2.为什么遍历列表时,key最好不要用index
*
* 虚拟DOM中的作用
* 1.简单来说:key就是虚拟DOM对象的标识,更新显示key起着极其重要的作用
* 2.详细来说:当状态的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后react进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下
* a.旧的虚拟DOM中找到了与新的虚拟DOM相同的key
* a1.若虚拟DOM中内容没有改变,直接使用之前的真实DOM
* a2.若虚拟DOM中内容改变,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
* b.旧的虚拟DOM中未能找到与新的虚拟DOM中相同的key
* b1.根据数据创建新的真实DOM,随后渲染到界面
*
* 用index作为key可能会引发的问题
* 1.若对数据进行:逆序添加/删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没问题,但是效率低
* 2.如果结构中还包含输入类的DOM,会产生错误DOM更新==>界面有问题
* 3.注意!如果不存在对数据的逆序添加/删除等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
*
* 开发中如何选择key
* 1.最好使用每条数据的唯一值作为key,比如id/手机号/身份证/学号等
* 2.如果缺点只是简单的展示数据,用index也是可以的
*/
class ClassRefsCom extends React.Component {
state = {
msg: '欢迎实用react',
personsArr: [
{
id: 1, name: '张三' },
{
id: 2, name: '李四' }
]
}
addPeople = () => {
let {
personsArr } = this.state;
personsArr.unshift(
{
id: 3, name: '王五' },
)
this.setState({
personsArr: personsArr
})
}
render() {
let {
personsArr } = this.state;
let arr = [];
personsArr.forEach((item, index) => {
arr.push(
/**
* 使用index索引值做key
*
* 初始化数据:
* [
* { id: 1, name: '张三' },
* { id: 2, name: '李四' }
* ]
* 初始的虚拟DOM:
* <li key=0>张三</li>
* <li key=1>李四</li>
*
* 更新后的数据
* [
* { id: 0, name: '王五' },
* { id: 1, name: '张三' },
* { id: 2, name: '李四' }
* ]
* 更新数据后的虚拟DOM
* <li key=0>王五<input style={
{ width: '50px' }} type="text" /></li>//更新
* <li key=1>张三<input style={
{ width: '50px' }} type="text" /></li>//更新
* <li key=2>李四<input style={
{ width: '50px' }} type="text" /></li>//更新
*
* 这样做导致的问题,
* 1.由于初始化的虚拟DOM中找不到和更新后的虚拟DOM中相同的key,所以,整个arr就都会更新,这样就直接导致了效率极低,但是从内容上来说,张三/李四并没有修改,只是数组的unshift方法修改了数组的index,导致key全部修改了,从而致使页面全部更新,其实这样的更新是没有必要的
* 2.input会复用,但是由于破坏了arr的结构,导致input对应出了问题
* --------------------------------------
* 使用唯一值做key
*
* 初始化数据:
* [
* { id: 1, name: '张三' },
* { id: 2, name: '李四' }
* ]
* 初始的虚拟DOM:
* <li key=0>张三</li>
* <li key=1>李四</li>
*
* 更新后的数据
* [
* { id: 0, name: '王五' },
* { id: 1, name: '张三' },
* { id: 2, name: '李四' }
* ]
* 更新数据后的虚拟DOM
* <li key=3>王五<input style={
{ width: '50px' }} type="text" /></li>//更新
* <li key=1>张三<input style={
{ width: '50px' }} type="text" /></li>//复用
* <li key=2>李四<input style={
{ width: '50px' }} type="text" /></li>//复用
*
* 这样做,更新之后张三/李四的key/value和初始化是一样的,这样只会更新王五对应的DOM,大大提高了效率
*/
<li key={
item.id}> <input style={
{
width: '50px' }} type="text" /> {
item.name}</li>
)
})
console.log(arr);
return (
<div className="wrap">
<ul>
{
arr}
</ul>
<button onClick={
this.addPeople}>加人</button>
</div>
)
}
}
ReactDOM.render(<ClassRefsCom name='xiaobaocheng' count={
996} />, document.querySelector('#container'))
13.初始化React脚手架
13.1环境搭建
- 全局安装:npm install create-react-app -g
- 创建项目:create-react-app ‘自定义项目名称’
- 进入项目文件夹:cd [‘自定义项目名称’]
- 启动项目:npm start
13.2脚手架文件解读
- public ⇒ 静态资源文件夹
- favicon.ico ⇒ 网站页签图标
- index.html ⇒ 主页面
- 内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <!-- %PUBLIC_URL%代表public文件夹的路径 --> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <!-- 用于开启理想视口,用于做移动端的适配 --> <meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- 用于配置浏览器页签+地址栏的颜色(仅仅支持安卓手机) --> <meta name="theme-color" content="#000000" /> <!-- 描述 --> <meta name="description" content="Web site created using create-react-app" /> <!-- 用于指定网页添加到主屏幕的图标(仅仅支持苹果手机) --> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- 引用加壳时的配置文件 --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <title>React App</title> </head> <body> <!-- 如果浏览器不支持js的允许,才会显示 --> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>
- 内容
- logo192.png ⇒ logo图
- logo512.png ⇒ logo图
- manifest.json ⇒ 应用加壳的配置文件
- robots.txt ⇒ 爬虫协议文件
- src ⇒ 源码文件夹
- App.css ⇒ App组件的样式
- App.js ⇒ App组件
- App.test.js ⇒ 用于给App做测试
- index.css ⇒ 通用样式
- index.js ⇒ 入口文件
- logo.svg ⇒ logo图
- reportWebVitals.js ⇒ 页面性能分析文件(需要web-vitals库的支持)
- setupTests.js ⇒ 组件单元测试文件(需要jest-dom库的支持)
13.3脚手架配置请求代理
- 在
src
文件夹下创建setupProxy.js
文件 只能使用commonJS
规范
const proxy = require('http-proxy-middleware');
module.exports = function (app) {
app.use(
proxy('/api', {
//遇见/api前缀的请求,就会触发该代理配置
target: 'http://localhost:5000',//请求转发给谁
changeOrigin: true,//控制服务器收到的响应头中Host字段的值
pathRewrite:{
//重写请求路径 如果不设置这个参数服务器接到的地址就是/api/路径;设置了路径就是/路径
'^/api':''
}
}),
proxy('/api1', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite:{
'^/api1':''
}
})
)
}
14组件通信
14.0-nanoid-唯一字符串 ID 生成器
import {
nanoid } from 'nanoid';
console.log(nanoid());//生成唯一的id 参数可以是数字
14.1组件通信-后代组件向祖先组件发送数据
class GrandsonCom extends React.Component {
state = {
msg: 'grandson'
}
sendDataFromGrandsonCom = () => {
let {
getGrandsonValue } = this.props;
getGrandsonValue({
grandson: '来自孙子组件的值'
})
}
render() {
let {
msg } = this.state;
return (
<div className="grandson">
<span>{
msg}</span>
<button onClick={
this.sendDataFromGrandsonCom}>点击按钮向祖父组件发送数据</button>
</div>
)
}
}
class ChildrenCom extends React.Component {
state = {
msg: 'children'
}
sendDataFromChildrenCom = () => {
let {
getChildrenValue } = this.props;
getChildrenValue({
children: '来自孩子组件的值'
})
}
render() {
let {
getGrandsonValue } = this.props;
return (
<div className="children">
{
this.state.msg}
<button onClick={
this.sendDataFromChildrenCom} >点击按钮向父组件发送数据</button>
<GrandsonCom getGrandsonValue={
getGrandsonValue} />
</div>
)
}
}
class FatherCom extends React.Component {
state = {
msg: 'father'
}
getChildrenValue = (childrenValue) => {
console.log('获取到的孩子组件的值', childrenValue);
}
getGrandsonValue = (childrenValue) => {
console.log('获取到的孙子组件的值', childrenValue);
}
render() {
return (
<div className="father">
{
this.state.msg}
<ChildrenCom getChildrenValue={
this.getChildrenValue} getGrandsonValue={
this.getGrandsonValue} />
</div>
)
}
}
ReactDOM.render(<FatherCom />, document.querySelector('#container'))
14.2组件通信-任意组件通信/发布订阅者模式
- 工具库:PubSubJS
- 下载:npm install pubsub-js -S
class ChildrenCom1 extends React.Component {
state = {
msg: 'children1'
}
sendDataFromChildrenCom = () => {
PubSub.publish('sendDataFromChildrenCom1', {
//在ChildrenCom1中发布消息
name: "children1",
value: "你好啊,ChildrenCom2"
})
}
render() {
return (
<div className="children">
{
this.state.msg}
<button onClick={
this.sendDataFromChildrenCom} >点击按钮向ChildrenCom1发送数据</button>
</div>
)
}
componentDidMount() {
//在componentDidMount中订阅消息
this.token = PubSub.subscribe('sendDataFromChildrenCom2', (msg, data) => {
console.log('children1', msg, data);
})
}
componentWillUnmount() {
//在组件将要卸载的时候取消订阅消息
PubSub.unsubscribe(this.token);
}
}
class ChildrenCom2 extends React.Component {
state = {
msg: 'children2'
}
sendDataFromChildrenCom = () => {
PubSub.publish('sendDataFromChildrenCom2', {
//在ChildrenCom2中发布消息
name: "children2",
value: "你好啊,ChildrenCom1"
})
}
render() {
return (
<div className="children">
{
this.state.msg}
<button onClick={
this.sendDataFromChildrenCom} >点击按钮向ChildrenCom2发送数据</button>
</div>
)
}
componentDidMount() {
//在componentDidMount中订阅消息
this.token = PubSub.subscribe('sendDataFromChildrenCom1', (msg, data) => {
console.log('children2', msg, data);
})
}
componentWillUnmount() {
//在组件将要卸载的时候取消订阅消息
PubSub.unsubscribe(this.token);
}
}
class FatherCom extends React.Component {
state = {
msg: 'father'
}
getChildrenValue = (childrenValue) => {
console.log('获取到的孩子组件的值', childrenValue);
}
getGrandsonValue = (childrenValue) => {
console.log('获取到的孙子组件的值', childrenValue);
}
render() {
return (
<div className="father">
{
this.state.msg}
<ChildrenCom1 />
<ChildrenCom2 />
</div>
)
}
}
ReactDOM.render(<FatherCom />, document.querySelector('#container'))
antd组件库的使用
- 下载 cnpm install ant-S
- 使用
import 'antd/dist/antd.css';
import { Button } from 'antd';
- antd的按需引入和自定义主题
1.安装依赖:cnpm install react-app-rewired customize-cra babel-plugin-import less less-loader
2.修改package.json
...
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test"
},
3.根目录下创建config-overrides.js
//配置antd按需引入css的规则
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style:true,
}),
addLessLoader({
lessOptions:{
javascriptEnabled:true,
modifyVars: { '@primary-color': '#1DA57A' },
}
})
);