1.用vite启动一个react项目
参考官网文档:cn.vitejs.dev/guide/
2.第一个Component
在react中,函数式组件必须以大写开头,在main.tsx中写一个测试组件
import ReactDom from "react-dom"
function Greeting() {
return <h1>This is my ReactProject</h1>
}
ReactDom.render(<Greeting></Greeting>,document.getElementById('root'))
通过ReactDom.render渲染函数就在index.html中找到了id=root的位置把组件渲染了出来
(在react17之后,不再需要写 import React from 'react'
了)
3.第一个Component的补充
在函数组件里,可以不return一个HTML,可以return一个react子组件
import React from 'react'
const Greeting = () => {
return React.createElement('h1', {}, 'Hello, World')
}
ReactDom.render(<Greeting></Greeting>,document.getElementById('root'));
4.JSX规则
(1)只return一个元素
function Greeting() {
return (
<h1>First</h1>
<h2>Second</h2>//不能return两个元素
)
}
function Greeting() {
return (
<>
<React.Fragment> //可以用别的元素包起来
<h1>First</h1>
<h2>Second</h2>
<React.Fragment/>
</>
)
}
(2)HTML属性需要用驼峰写法(HTML开头小写来和大写开头的React组件区分)
通常情况下的HTML:
<div onclick="console.log('hello')">normal HTML</div>//注意onclick
在JSX里:
const Greeting = () => {
// return React.createElement('h1', {}, 'Hello, World')
return (
<div>
<h1 onClick={console.log("hello")}>hello</h1>
</div>
)
}
(3)类用ClassName表示
通常情况下的HTML:
<div class="class"> class </div>
在JSX里:
const Greeting = () => {
// return React.createElement('h1', {}, 'Hello, World')
return (
<div>
<h1 onClick={console.log("hello")}>hello</h1>
<h2 className="className">class</h2>
</div>
)
}
(4)所有标签都要闭合(img、input
这类)
5.如何写嵌套组件
如果直接再写一个Nested组件是无法显示的,需要把第二个组件放到第一个里面
const Greeting = () => {
// return React.createElement('h1', {}, 'Hello, World')
return (
<div>
<Nested></Nested>
<h1 onClick={console.log("hello")}>hello</h1>
<h2 className="className">class</h2>
</div>
)
}
const Nested = () => {
return (
<div>nested component</div>
)
}
ReactDom.render(<Greeting></Greeting>,document.getElementById('root'));
6.为什么会有大括号
为什么是onClick = {}
,这里用大括号代表{JavaScript空间},表示从react进入到了JavaScript中,所以如果需要在里面写对象,就变成两个花括号了onClick = { {} }
,但这真实意思是在JavaScript空间里放一个object对象{}
const Greeting = () => {
// return React.createElement('h1', {}, 'Hello, World')
return (
<div>
<h1 onClick={console.log("hello")}>hello</h1>
</div>
)
}
7.组件传值
(1)父传子props
在子组件中设置参数props,在父组件中设置相应的值(可以是字符串或表达式),可传递给子组件
import ReactDom from "react-dom"
const number = 12
const Father = () => {
return (
<Son message="msg" num = {number}></Son>
)
}
const Son = (props) => {
return (
<>
<div>Son</div>
<p>{props.num}</p>
</>
);
}
ReactDom.render(<Father></Father>, document.getElementById('root'));
(2)解构赋值(destructure)
还可以利用JavaScript的解构赋值简化代码(只利用了JavaScript,和React无关)
const number = 12
const Father = () => {
return (
<Son message="msg" num = {number}></Son>
)
}
const Son = (props) => {
const {num, message} = props
return (
<>
<div>Son</div>
<p>{num}</p>
<p>{message}</p>
</>
);
}
再进一步,把props用{ }代替掉:
const number = 12
const Father = () => {
return (
<Son message="msg" num = {number}></Son>
)
}
const Son = ({num, message}) => { //注意这里
// const {num, message} = props //不再需要props
return (
<>
<div>Son</div>
<p>{num}</p>
<p>{message}</p>
</>
);
}
(3)子组件插槽
可以注意到,如果在子组件中放入一段话是无法被显示出来的,因为他不知道应该把这段话放到哪
<Son message="msg" num = {number}>This senten will not be dispaly</Son>
如果在子组件中放入了东西,那么他的props就会自动生成一个叫children
的属性,就相当于一个插槽,把他放在子组件中,就可以在相应位置渲染出来
const Father = () => {
return (
<>
<Son message="msg" num = {number}>This senten will not be dispaly</Son> //自动会有个`children`属性,不能再声明`children`属性
<Son message="msg2" num = {number+1} children="child"></Son> //innerText里没有内容,可以再声明一个`children`属性
</>
)
}
const Son = ({num, message, children}) => {
return (
<>
<div>Son</div>
<p>{num}</p>
<p>{message}</p>
{children}
</>
);
}
8.列表渲染
(1)渲染数组
可以直接用map函数渲染数组:
const items = ['first', 'second', 'third'];
const itemRender = items.map((item) => {
return (
<div>{item}</div>
);
});
console.log(itemRender) //可以看到里面的结构是虚拟DOM
const ListRender = () => {
return (
<div>{itemRender}</div>
)
};
ReactDom.render(<ListRender/>, document.getElementById('root'));
(2)渲染对象
列表渲染不能渲染对象,只能渲染数组,需要用一个map函数把要渲染的html保存到数组(虚拟DOM),然后放到组件中进行渲染:
const items = [ //外面要包一层数组
{
name: 'first',
img: 'img1',
mesg: 'mesg1'
},
{
name: 'second',
img: 'img2',
mesg: 'mesg2'
}
];
const itemRender = items.map((item) => {
const {name, img, mesg} = item
return (
<>
<div>{name}</div>
<div>{img}</div>
<div>{mesg}</div>
</>
);
});
console.log(itemRender);
const ListRender = () => {
return (
<div>{itemRender}</div>
)
};
ReactDom.render(<ListRender/>, document.getElementById('root'));
(3)渲染组件
map除了列表渲染对象数组之外,也可以列表渲染组件,同时为了优化虚拟dom渲染流程,应该添加一个每个数据独有的值作为key值:
const items = [
{
name: 'first',
img: 'img1',
mesg: 'mesg1',
id: 1
},
{
name: 'second',
img: 'img2',
mesg: 'mesg2',
id: 2
}
];
const ItemComponent = ({name, img, mesg}) => {
return (
<>
<div>{name}</div>
<div>{img}</div>
<div>{mesg}</div>
</>
)
}
const ListRender = () => {
return (
<div>
{
items.map((item) => {
const {name, img, mesg, id} = item
return (
<ItemComponent key={id} name={name} img={img} mesg={mesg} ></ItemComponent>
)
})
}
</div>
)
};
ReactDom.render(<ListRender/>, document.getElementById('root'));
如果嫌这样写组件要传进去的太多了也可以用对象包裹一下,但要注意的是传进去后外面会多包一层对象
const ItemComponent = (props) => {
const {name, img, mesg} = props.item //传进来之后加了一层{}
return (
<>
<div>{name}</div>
<div>{img}</div>
<div>{mesg}</div>
</>
)
}
const ListRender = () => {
return (
<div>
{
items.map((item) => {
// const {name, img, mesg, id} = item
return (
<ItemComponent key={item.id} item={item} ></ItemComponent>
)
})
}
</div>
)
};
ReactDom.render(<ListRender/>, document.getElementById('root'));
再简化一步,使用...
三个点扩展运算符
const ItemComponent = ({name, img, mesg}) => { //传过来就可以直接用
// const {name, img, mesg} = props
return (
<>
<div>{name}</div>
<div>{img}</div>
<div>{mesg}</div>
</>
)
}
const ListRender = () => {
return (
<div>
{
items.map((item) => {
// const {name, img, mesg, id} = item
return (
<ItemComponent key={item.id} {...item} ></ItemComponent>
)
})
}
</div>
)
};
ReactDom.render(<ListRender/>, document.getElementById('root'));
9.useState
由于纯函数的原因,在函数式组件中申明一个变量,之后改变这个变量不会触发re-render。组件只有在state或者props改变时才会触发re-render。而在组件里改变state并保留状态就需要用useState。
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('');
function handleClick() {
setMessage(`You have clicked the button ${count} times`);
}
return (
<div>
<button onClick={handleClick}>Click me</button>
{/* Render the message */}
{message}
</div>
);
}
10.useEffect
useEffect(() => {
当依赖数组中任意一个依赖改变时触发
console.log('Data has changed:', data);
//在组件卸载或依赖改变的时候触发
return () => console.log('Cleanup'); }, [data]);
},[data]);
(1)useEffect是一种副作用(side effect), 在渲染之后才执行,调用useEffect不不触发re-render,React组件只在state和props改变的时候才会触发re-render
(2)useEffect的return函数在组件卸载或依赖改变的时候触发
(3)依赖数组有三种情况,不加依赖数组,加一个空的依赖数组[]
,在依赖数组里添加依赖[data2,data2]
useEffect(() => { //什么都不加
console.log('Data has changed:', data);
});
useEffect(() => { //加一个空的依赖数组
console.log('Data has changed:', data);
},[]);
useEffect(() => { //在依赖数组里添加依赖data1,data2
console.log('Data has changed:', data);
},[data1,data2]);
这三种的区别是:不加依赖数组,在初始化组件,在每次渲染组件(组件中任意变量变了触发重渲染)时触发useEffect。加一个空的依赖数组[]
,只在初始化组件时触发。在依赖数组里添加依赖[data2,data2]
,在组件初始化和data1或data2改变时触发。
11.useRef
useRef包裹的值在改变时不会触发re-render,可以用在例如<input>
和<form>
里,如果我们不希望在<input>
里每次输入一个值,就触发一次重渲染,我们便可以使用useRef。