theme: channing-cyan
react学习前置准备
第一步安装vite
在项目中初始化vite
在命令行中全局安装vite
npm i -g vite
npm create vite
在vite中选择react的基础配置
根据提示选择对应的框架即可
在跳转到建立的项目文件夹中
cd ***(创建的项目文件夹的名字)
配置vite的依赖包
npm i
执行对应的vite命令即可
npm dev
React的基本知识
(在vite工具的帮助下,不需要注意这些)
基本的React了解
名称 介绍 babel.min.js 主要将Jsx转换成js格式 react.development.js React核心库 React-Dom.delelopment.js 用于支持React操作Dom引入的顺序
- 先引入核心库react.development.js
- 在引入react-dom,用于支持react操作dom
- 最后引入babel,用于将Jsx转换成js,让后创建dom
- 如果使用vite等打包脚手架工具,则不需要以上步骤
写react的准备
-
写react代码要注明
<script type="text/babel">
在末尾引入,这样浏览器才知道写的是Jsx的代码
-
控制台上favicon.ico 的警告提醒,在项目文件的根目录中放一张ico偏爱图标即可,图标名字一定要为favicon.ico,不然浏览器查询不到
了解react的简单使用方法
开始准备容器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
//引用react
<script type="text/javascript" scr="../js/react.development.js"></script>
<script type="text/javascript" scr="../js/react-dom.development.js"></script>
<script type="text/javascript" scr="../js/babel.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="test"></div>
//准备容器,用于放置react渲染的dom
</body>
</html>
创建虚拟Dom,并渲染到界面上
<script type="text/babel">
//创建虚拟dom
const vm=<h1>hello,react</h1>
//也可以写成括号包裹形式,便于观看,如下
const vm=(
<h1>hello,react</h1>
)
//通过js创建虚拟dom,这样不用引入babel.min.js
const vm= react.createElement('h1',{id:'test'},'hello,react')
//渲染虚拟dom到页面
ReactDom.render(vm,document.getElentById"test")
</script>
jsx最终在浏览器中呈现的格式都是js模式,如上方格式,不过常用的是其语法糖模式
如果用js创建虚拟dom,第一部分写标签名,第二部分写标签属性,以对象的形式写,可在内写多个属性,第三部分写标签内容
标签内容中可嵌套
const vm= react.createElement('h1',{id:'test'},react.createElement('span',{id:test1},'你好'))
关于虚拟DOM
-
本质是object类型的对象
-
因为虚拟dom在react的内部使用,不需要太多的属性,所以虚拟dom的属性较少,所以比较"轻",速度更快
-
虚拟DOM最终会被react转换成真实DOM,呈现在页面
-
jsx是resct定义的一种类xml的js扩展语法
xml语法
<student>
<name>tom</name>
<age>18</age>
</student>
xml语法现在基本被json语法替代
{
'name':'tom',
'age':'18'
}
jsx的语法规则
-
定义虚拟DOM时不要写写引号
-
标签中混入js表达式时要用{}号包裹
-
样式的类名指定时不要用class,用className,避免与js语法混搅
-
jsx中css的内联样式要用style={{key:value}}的双括号形式去写,原本js中要引用css内联样式应写style="key:value"
-
虚拟标签dom只用一个根标签,一个根标签对应一个虚拟DOM,如果有多个同级虚拟DOM,要用一个标签将其包裹
-
标签必须闭合,所以像input这种单标签,要写成以下形式
<input type='text'/>
-
标签首字母,如果是小写字母,则将标签转成html的同名标签元素;如果是大写字母开头,则将其渲染成react组件
小例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
//引用react
<script type="text/javascript" scr="../js/react.development.js"></script>
<script type="text/javascript" scr="../js/react-dom.development.js"></script>
<script type="text/javascript" scr="../js/babel.min.js"></script>
<title>Document</title>
</head>
<body>
<div id="test"></div>
//准备容器,用于放置react渲染的dom
</body>
<script type="text/babel">
const myId='zs'
const myData='hello'
//创建虚拟dom
const vm=(
<div>
//假设title为假设外联式的css
<h2 className='title' id={myId.tolowerCare()}>
<span style={{color:'white',font:'29px'}}>{myData.tolowerCare()}</span>
</h2>
<input type='text'/>
</div>
)
</script>
</html>
简单理解:
- 表示固定的字符串就用正常的 ' ' 和 " " 的引号,如 className='title'
- css内联式用{{ }}号
- 自定义的js表达式就用{ }号
- 类名用className
- 原因解析:原js语法中标签都是' '形式,都是字符串,没有变量的概念;所以jsx语法中用js表达式(本质上是变量赋值)时用的是{ }号;而css内联式是键值对的形式,便不可以用单括号,便用{{ }}双括号来表示
小例子 动态将数组中的名称建立到列表标签中
<script type="text/babel">
const data=['zs','ls','ww']
const vm=(
<div>
<ul>
{data.map((item,index)=>{
return <li key={index}>{item}</li>
//react为了diff算法,其中的标签中都要赋予唯一的key值
//例子中的key值赋予的有问题,在进行数组拼接时会出现key值重复
}
)}
</ul>
</div> )
</script>
函数式组件
<script type="text/babel">
function Dome(){
return <h2>函数式组件</h2>
}
//渲染虚拟dom到页面
ReactDom.render(<Dome/>,document.getElentById"test")
</script>
该自定义函数this本应指向window,但是经过babel翻译后,开启了严格模式,进制来this指向window指向window,所以查看时,该this为undefined
严格模式开启 "use strict"
- react解析组件标签,找到Component组件
- 发现是组件是函数定义,随后调用该函数,将虚拟Dom转为阵势dom
- 呈现在页面中
函数式组件的props
props
传递过来的属性是只读的,如果想修改可以弄一个值接受一下,但是不能直接操作props
Object.freeze(obj)冻结,不能修改,不能增删,不能劫持
Object.seal(obj)密封,能修改,不能增删,不能劫持
Object.preventExtensions(obj)不可扩展,能修改,不能新增
import React from "react";
import PropTypes from 'prop-types';
Person.propTypes={
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
}
//对函数组件的props做限制,因为不是类,所以不能用类的静态方法,只能使用函数Person中方法
//如果使用ts,该插件可以不再使用
Person.defaultProps = {
sex: "男",
age: 18,
};
//与上同理
function Person(props){
//创建函数并接受props
const {name,sex,age}=props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age + 1}</li>
</ul>
);
}
export default Person;
props.children
子组件中如果不存在内容,props.children,就为null
子组件中如果存在一个标签,props.children,就为该标签的对象,可以在大胡子语法中直接渲染
props一般是用来传值,而props.children
是 React 中的一个特殊属性。它允许你在组件标签内传递子元素。例如:
<MyComponent>
<p>Hello, world!</p>
</MyComponent>
在 MyComponent
组件内部,可以通过 props.children
属性来访问子元素 <p>Hello, world!</p>
。这样,MyComponent
组件就会将子元素 <p>Hello, world!</p>
显示在自己的内容区域中。
function MyComponent(props) {
return (
<div>
{props.children}
</div>
);
}
子组件中如果存在多个标签,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置
<MyComponent>
<p>Hello, world!</p>
<p>你好,世界</p>
</MyComponent>
function MyComponent(props) {
return (
<div>
{props.children[1]}//你好,世界
{props.children[0]}//Hello, world!
</div>
);
}
子组件中存在多个标签,还可以给组件子元素加上一个slot='???'的属性作为名字,props.children,就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置
<>
<Hello>
<p slot="0">123</p>
<p slot="1">234</p>
</Hello>
</>
<>
<div>
{props.children.map((item) => {
if(item.props.slot==="1"){
//注意item.props,是props.children.props不是props
return item
}
})}
{props.children.map((item) => {
if(item.props.slot==="0"){
//注意item.props,是props.children.props不是props
return item
}
})}
//这么写也行
<>
<div>
{props.children.map((item) => {
return item.props.slot==="1"&&item
})}
</div>
</>
</div>
</>
子组件被多次调用,还可以给组件加上一个slots='???'的属性作为组件名字,props.children就为该标签的对象数组,可以在大胡子语法中选择性渲染,以及调整渲染位置 一般是弄个变量在上面接收好,再在下面使用,避免下面的代码太长
<>
<Hello>
<p slot="0">123</p>
<p slot="1">234</p>
</Hello>
<Hello slots="3">
<p >456</p>
</Hello>
</>
<>
<div>
{
props.slots=='3'&&props.children
}
{
props.slots==null&&props.children
}
</div>
</>
props.render
在React中,一个组件的props是通过props属性传递给它的,可以使用props来获取组件传递的数据。而render函数是一种用来创建组件的方法,它可以将组件渲染到页面上。可以将render函数作为组件的一个属性传递给组件,并在组件的render方法中调用该属性。这样,可以实现动态生成DOM元素或者执行一些逻辑的功能。
例如,可以定义一个MyComponent组件,并将一个带有render函数的props传递给它,如下所示:
<MyComponent render={() => <span>Hello World</span>} />
function MyComponent(props) {
return <div>{props.render()}</div>;
}
在上面的例子中,MyComponent组件接收一个名为render的props,它是一个函数,返回一个带有Hello World文本的span元素。在MyComponent组件的render方法中,调用了props.render函数,并将其返回的元素渲染到了页面上。
通过这种方式,可以在组件内部动态生成DOM元素,并且可以根据需要实现更为复杂的功能。
两者的区别
props.render
是一个函数,它接收一些参数并返回一个组件。通常用于在父组件中动态地渲染子组件。例如:
function Parent(props) {
return (
<div>
{props.render()}
</div>
)
}
function Child() {
return (
<h1>Hello, world!</h1>
)
}
function App() {
return (
//在标签内属性定义一个回调
<Parent render={() => <Child />} />
)
}
props.children
表示组件的子元素。它可以是一个组件,也可以是多个组件。React会将children作为props传递给父组件,从而允许我们在组件内部使用组件。例如:
function Parent(props) {
return (
<div>
{props.children}
</div>
)
}
function Child1() {
return (
<h1>Hello, world!</h1>
)
}
function Child2() {
return (
<p>This is a paragraph</p>
)
}
function App() {
return (
//在标签内容定义组件
<Parent>
<Child1 />
<Child2 />
</Parent>
)
}
因此,props.render
用于动态地渲染子组件,而props.children
用于将已经存在的组件作为父组件的子元素。
函数式组件的useState
useState的使用
import React from 'react'
function Index () {
//类式组件改变状态的样式,和下面函数式组件对比学习
// state={count:0}
//add=() => {
//this.setState({count:count+1})
//}
//对状态的数组进行解构赋值,数组第一个参数是状态名;第二个参数是改变状态的函数。相当于setState
const [count,setCount]=React.useState(0)
const [name,setName]=React.useState('jack')
function add() {
//在事件回调的函数中触发对应状态的函数
setCount((count) => {
return count+1}
)
}
function changeName() {
//像这种简单的改变,直接向set***中传入参数即可
setName('tom')
}
return (
<div>
<h1>当前求和为{count}</h1>
<h1>{name}</h1>
<button onClick={add}>点击加一</button>
<button onClick={changeName}>点击换名</button>
</div>
)
}
export default Index
闭包和消息队列的骚扰
import { useState } from "react";
function Hello() {
const [count, setCount] = useState(0);
const [name, setName] = useState("jack");
// 函数式组件每次渲染,都是函数的重新创建,所以就产生了闭包问题
//useState,只有第一次设的初始值会生效,后续的值都是setState函数的返回值
//也是因为useState只有第一次会执行,所以像对初始数据处理的行为,可以直接在useState里写个回调处理
//重新创建的组件函数拿到setState函数的返回值,实现界面上数值的更新
//因为新值是新创建的组件函数拿到的,所以在这里不经过处理拿到的都是没修改前的旧值
function add0() {
console.log("因为闭包只能显示旧值,会比界面上现实的值小一:",count);
setCount(count + 1);
}
function add1() {
for (let i = 0; i < 10; i++) {
setCount(count + 1);
}
}
//结果并不会加十,因为这里每次只能读到旧值
//所以虽然执行了10次,但是每次读取都是0,进行了十次0+1,结果就为1
function add2() {
for (let i = 0; i < 10; i++) {
setCount((count) => {
return count + 1;
});
}
}
//结果会加十,因为这里每里用了回调函数实现了闭包,使值在回调函数中被记录了下来
//所以结果就为11
function changeName() {
setName("tom");
}
return (
<div>
<h1>当前求和为{count}</h1>
<h1>{name}</h1>
<button onClick={add0}>闭包,点击加一</button>
<button onClick={add1}>错误的点击加十</button>
<button onClick={add2}>正确的点击加十</button>
<button onClick={changeName}>点击换名</button>
</div>
);
}
export default Hello;
结论
state简单的修改就用直接用
涉及到对值重复修改就要考虑闭包问题,最好使用回调函数进行闭包记值
setStata修改出现的问题
import { useState } from "react";
function Hello() {
const [count, setCount] = useState({
boyNum: 100,
girlNum: 100,
});
function addBoy() {
setCount({
...count,
//setState,每次都是将值重新赋值给新渲染的组件函数,所以不能单独渲染某一个值
//要将原本的值全部展开,再修改某一个值,不然那些值就会丢失
//如果一开始就想只修改特定的值,建议直接那些值拆开,不要放在一个对象
boyNum: count.boyNum + 1
})
}
function addGirl() {
setCount({
...count,
girlNum: count.girlNum + 1
})
}
return (
<div>
<h2>当前人数为{count.boyNum + count.girlNum}</h2>
<h3>男生人数为{count.boyNum}</h3>
<h3>女生人数为{count.girlNum}</h3>
<button onClick={addBoy}>男生加1</button>
<button onClick={addGirl}>女生加1</button>
</div>
);
}
export default Hello;