前言
学习了前端的html,js,css 。还是得了解一下前端的框架有名的框架React,主要学习下react的思想和关键使用方法
JSX
定义规则
- 只能返回一个根元素
- 标签必须闭合
- 使用驼峰式命名法给 大部分属性命名
index
=>tabIndex
class
=>className
for
=>htmlFor
有关{}
的使用
在书写Html内容那边,想加入js相关的动态变量内容的引用或者计算表达式,用{}
来标记
css
定义:const element = Hello, world!;
使用:<div>{element}</div>
<h1>{name}'s To Do List</h1> // 有效
<{tag}>gjc To Do List</{tag}> // 无效
有时候在JSX 中看到 {{
和 }}
时,其实是包在大括号里的一个对象
css
<div person={{ name: "Hedy Lamarr", inventions: 5 }}></div>
<ul style={{backgroundColor: 'black',color: 'pink'}}>
针对如下使用场景的{}
的用法
- 表示
true
或者false
- 表达式书写花括号中
- 具体对象中的参数获取
- 使用三目运算代替if和else
- 内联样式
html
标签调用方法js
中表达整个标签对象js
中表达整个标签数组
javascript
<div maskClosable={false}></div>
<div>{1+1}</div>
<div>{this.state.name}</div>
第一种:<div>{i == 1 ? 'True!' : 'False'}</div>
第二种:className={ this.state.curId === item.id?'active':'asideItem'}
第三种:style={this.state.curId === item.id ? {fontSize:'20px'}:{fontSize:'12px'}}
定义 var myStyle={fontSize: 100, color: '#FF0000' };
使用 <div style= {myStyle}/>
function formatName(user){return user.name}
function getGreeting(user) {
if (user) {return <h1>Hello, {formatName(user)}!</h1>;}
return <h1>Hello, Stranger.</h1>;
}
const element = (
<h1 className="greeting">Hello, world!</h1>
);
使用<div>{element}</div>
定义var arr = [ <div>11</div>,<div>22</div>]
使用<div>{arr}</div>
获取渲染列表内容
通过map
,filter
等函数对数组进行数据筛选,然后拼接成到html
上,return
返回
ini
const numbers = props.numbers;
const listItems = numbers.map((number) =>
<ListItem key={number.toString()}
value={number} />
);
return (
<ul>
{listItems}
</ul>
);
注意渲染列表: 直接放在 map()
方法里的 JSX
元素一般都需要指定 key
值(可以是数据库的主键id
,也可以是uuid
等自增唯一值)
逻辑判断渲染内容
- 通过
if..else
控制渲染内容的不同
ini
let button;
if (isLoggedIn) {
button = <LogoutButton onClick={this.handleLogoutClick} />;
} else {
button = <LoginButton onClick={this.handleLoginClick} />;
}
- 通过运算符
&&
通过在js上的上对运算符的短路原则了解,只有左边true的时候,右边才会执行,所以在这边就是右边元素会渲染
xml
<div>{isLoggedIn && <UserProfile />}</div>
- 三目运算符
?x:y
ini
{isLoggedIn? <LogoutButton onClick={this.handleLogoutClick} />
: <LoginButton onClick={this.handleLoginClick} />}
基础使用
创建项目
我们可以通过create-react-app
来实现创建react
脚手架
arduino
npx create-react-app demo // 创建项目
yarn start // 启动项目,yarn可以通过npm安装
函数组件
编写函数组件的形式的模板案例(vscode使用快捷键rfc
)
注意: 函数组件无法使用this
,点击事件绑定可以直接指定函数名或者箭头函数调用要指定的函数
javascript
export default function Text(props) {
// function 的方式形式
function text1(){}
// 箭头函数的形式
const text2 = () => {}
return (
<div>
<!-- 不能使用this -->
<Button type='primary' onClick={text2}>按钮</Button>
<Button type='primary' onClick={()=>test2()}>按钮</Button>
</div>
)
}
目前使用最多的还是函数组件,毕竟现在使用hooks
多,且函数组件支持hooks
写法。
类组件
编写类组件的形式模板案例(vscode使用快捷键rcc
)
注意:类组件中无法使用function,点击事件绑定函数时可以通过构造指定,也可以点击事件上指定。{this.xxClick.bind(this)}
scala
export default class Home extends Component {
// 箭头函数 ,调用: {() => this.button1Click("error")} / {this.button1Click}
button1Click = (type) => {
message[type](type)
}
// 无法使用function
// 1 .使用方法 , 且需要构造方法中this.buttonClick= this.buttonClick.bind(this); 调用:{this.buttonClick.bind(this)}
// 2, 获取调用时候绑定且能传值 {this.buttonClick.bind(this,1)}
buttonClick(){
alert("test")
}
render() {
return (
<div>
<Button type='primary'
onClick={() => this.button1Click("error")}>按钮</Button>
</div>
)
}
}
React的一些方法
React.createElement()
// 创建标签React.createClass()
// 创建类组件
javascript
import * as Icon from "@ant-design/icons";
const icon = React.createElement(Icon[item.icon])
const CreateClassCom = React.createClass({
render: function() {
return <div>这是React.createClass定义的组件</div>
}
});
空标签
<Fragment>
通常使用 <>...</>
代替,它们都允许你在不添加额外节点的情况下将子元素组合
xml
<>
<OneChild />
</>
Props
React
组件使用 props
来互相通信。每个父组件都可以提供 props
给它的子组件,从而将一些信息传递给它
注意:不要尝试更改 props
。 当你需要响应用户输入时,你可以去设置 state
父组件传递给子组件的值
传递两个参数:person
(一个对象)和 size
(一个数字)
javascript
export default function Profile() {
return (
<Avatar
person={{ name: 'Lin Lanying', imageId: '1bX5QH6' }}
size={100}
/>
);
}
子组件读取父组件传的值
- 方式一:通过对象解构
- 方式二:
props
对象接收然后读取值
javascript
// 方式一:通过对象解构
function Avatar({ name, imageId}) {
// 在这里 name 和 imageId是可访问的
}
// 方式二: props对象接收然后读取值
function Avatar(props) {
let name= props.name;
let imageId= props.imageId;
// ...
}
注意:接收的时候也可以指定参数的默认值
javascript
function Avatar({ name, imageId = 'xxxxx' }) {
// ...
}
组件的children
props
中含有children
属性,代表其包含的组件列表,所以可以通过拿到标签的children
做各种修饰效果
javascript
// 定义组件Card
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
组件Card
作用就是对其内部标签进行重新定义效果,有点类似装饰
ini
// 使用,此时Card的children则为<Avatar>...</Avatar>
<Card>
<Avatar person={{ name: 'Katsuko Saruhashi',imageId: 'YfeOqp2'}}/>
</Card>
// 最后效果等同于
<div className="card">
<Avatar person={{ name: 'Katsuko Saruhashi',imageId: 'YfeOqp2'}}/>
</div>
State
组件通常需要根据交互更改屏幕上显示的内容,我们单纯去修改变量的赋值,达不到这样的效果,所以得用State来保证重新渲染效果
基本使用
在保证使用新数据更新组件,需要保留渲染间的数据,还有就是触发React 使用新数据渲染组件,此时我们可以使用hook提供的useState方法
- State变量用于保存渲染件的数据
- State setter 函数 更新变量并触发 React 再次渲染组件
具体使用方法:通过定义一个名称和一个set函数方法,和useState
中设置初始值,具体使用案例如下所示中
javascript
const [index, setIndex] = useState(0);
function changeNumber() {
setIndex((index) => index + 1);
}
return (
<div>
<Button type="primary" onClick={changeNumber}>
按钮
</Button>
<div>{index}</div>
</div>
);
注意: setIndex(index+1)
这样使用会有问题,因为会合并所有更新状态且是异步执行,导致index
可能会是旧的值
使用场景
针对控制不同情况的变量,可以分开定义State
的值,对于那些时常一起变动的可以在一个State
上值为对象,对于修改对象字段时,要保证赋值的是新对象,才能触发渲染
- 修改
State
的值为对象内容的时候,单一修改个别里面字段
less
const [person,setPerson] = useState({})
setPerson({
...person, // 复制上一个 person 中的所有字段
firstName: e.target.value // 但是覆盖 firstName 字段
});
- 修改
State
的值为对象内容的时候,修改嵌套对象属性
less
const [person,setPerson] = useState({})
setPerson({
...person, // 复制其它字段的数据
artwork: { // 替换 artwork 字段
...person.artwork, // 复制之前 person.artwork 中的数据
city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!
}
});
- 修改
State
的值为数组内容的时候
这个时候就不能使用常规的crud数组了,也是需要构建新的数组
使用State的规范
- 合并关联的 state。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。
- 避免互相矛盾的 state。当 state 结构中存在多个相互矛盾或"不一致"的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。
- 避免冗余的 state。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。
- 避免重复的 state。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。
- 避免深度嵌套的 state。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。
注意: 在使用State
的时候,多个组件共同状态的State
可以提取到公共父组件,保证由上游的单一变化的State
联动控制下游组件。
scss
// 父组件
export default function Parent(){
// 上游的check控制
const [check,setCheck] = useState(false)
return (<div>
<Son check={check}>
</div>
)
}
// 子组件
export default function son({check}){
return (<div>
{check&&<Button>按钮<Button>}
<div>)
}
不同位置state的保留和移除
当我们定义了某个组件,在不同场景下使用该组件,他们各自维持的state都由自身去控制,数据是隔离的互不影响。当某个组件被移除的时候 ,组件中保存的state
也同步消失。此时我们要注意,对于同一组件UI树的位置发生变化的时候,其保存的内容才会消失
xml
// 组件在UI 树没变化
{checked?<Son checked={checked}>:<Son checked={checked}>}
// 组件在UI 树有变化
{checked?<p>nihao</p> :<Son checked={checked}>}
// 定义两块地方位置,组件在UI 树有变化
{isPlayerA &&<Counter person="Taylor" />}
{!isPlayerA &&<Counter person="Sarah" />}
// 通过key来区分不同组件
{checked?<Son key="k1" checked={checked}>:<Son key="k2" checked={checked}>}
注意: 组件不应该写成函数嵌套在其他组件内部,应该永远要将组件定义在最上层并且不要把它们的定义嵌套起来
javascript
export default function test(){
const [count, setCount] = useState(1);
function f1(){
const [text, setText] = useState('');
return (<div>
{test}
</div>)
}
return (<div>
<f1/>
<Button type="primary"
onClick={()=>setCount(count=>count+1)}>按钮</Button>
<div>)
}
// 以上情况就是每次渲染test组件时候,都会创建不同f1函数组件,
// 然后相当于你在相同位置渲染的是不同的组件,所以其下的state就都重置了
所以要想组件内的State
的内容保留,前提就是保证在同一位置,如果不想保留,就需要实现不同位置或者可以定义组件的Key来保证组件不同
Reducer
随着组件状态逻辑越来越多,我们把所有逻辑都可以存放在一个易于理解的地方。
定义增删改逻辑
定义的增删改查逻辑的方法内都通过给dispatch
传递一个对象action
可以由自己定义,可以命名type
标记行为,其他自己额外增加参数dispatch({action对象})
php
function add(){
dispatch({
type: 'add',
text:'内容',
id:1
});
}
function deleted(){
dispatch({
type: 'deleted',
id:1
});
}
声明和核心逻辑
useReducer
接收一个reducer
函数核心处理逻辑 和一个初始的State
javascript
// 声明
const [tasks, dispatch] = useReducer(tasksReducer, initialTasks);
const initialTasks = []
// 核心逻辑 reducer函数
function tasksReducer(tasks,action){
switch (action.type) {
case 'add': return [...task,{text:action.text,id:action.id}]
case 'deleted' : return tasks.filter((t) => t.id !== action.id);
default: {
throw Error('未知 action: ' + action.type);
}
}
}
可以在项目中同时使用 reducer
和state
自由搭配,对于简单逻辑使用state
,使用了reducer
在逻辑上更清晰和规整,方便调试和排查
Context
父子组件间传递值,通过props
一层一层的传递显得非常的不方便和冗长。
使用步骤:
- 创建 一个
context
,通过createContext
函数创建且赋值初始值 - 在需要数据的组件内 使用 刚刚创建的
context
,通过useContext
使用 - 在指定数据的组件中 提供 这个
context
,通过TestContext.Provider
提供值
scss
// 创建一个TestContext,赋值初始值
export const TestContext = createContext(1);
// 父组件,不同son组件上标记不同的值
function Father(){
return (<div>
<Son count={1}>
<GrandSon>
<GrandSon>
</Son>
<Son count={2}>
<GrandSon>
<GrandSon>
</Son>
</div>)
}
// 子组件获取标记上的值封装到TestContext上
function Son({ count, children }){
return (<TestContext.Provider value={count}>
{children}
</TestContext.Provider>)
}
// 孙子组件获取到UI树上最近的组件对应的的Context的值
function GrandSon(){
const count = useContext(TestContext);
}
上述案例的流程就是,将一个 count
参数传递给 <Son>
, Son
把它的子元素包在 <TestContext.Provider value={count}>
里面, GrandSon
使用 useContext(TestContext)
访问上层最近的 TestContext
提供的值
注意 : Context
会穿过中间层级的组件 , 让你在提供组件的Context
和使用它中间可以存在相隔很多个组件,且不同的 React context 不会覆盖彼此
使用场景 : 虽然Context
虽好,但是还是需要使用者合理的规划使用它,传统的Props
虽然传递繁琐,但是更加的直观可见。对于那些不传递数据的样式组件层可以通过children
包装传递,减少多余的组件层传递
kotlin
//使用Layout布局,没有涉及到数据使用
<Layout data={data}>
function Layout({data}){
return (<div>
<Data data={data}>
<div>)
}
// 可以直接通过chidren包装传递,减少组件间传递
<Layout>
<Data data={data}>
</Layout>
function Layout({chidren}){
return (<div>
{chidren}
<div>)
}
Ref
用于组件记住信息,但不重新触发渲染。
基本使用
使用步骤如下:
- 声明
const count = useRef(0)
- 获取到
ref
的对象结果为{current:0}
- 修改
ref
的值count.current = 1
ini
const count = useRef(0);
return (
<div>
<div>{count.current}</div>
<Button
type="primary"
onClick={() => {
count.current = count.current + 1;
alert(count.current);
}}
>按钮</Button>
</div>
);
注意: 修改ref的值,不会触发重新渲染 ,alert
中输出的值会一直递增+1, 但div
中的值一直都是0
具体的使用场景:存储不影响渲染逻辑的内容 (timeOutId,Dom元素等)
ref操作DOM
ini
const myRef = useRef(null);
<div ref={myRef}>
// 你可以使用任意浏览器 API,例如:
myRef.current.scrollIntoView();
获取标签dom节点,直接在标签上绑定属性ref,此时React 会把对该节点的引用放入 myRef.current
javascript
function Parent(){
const myRef = useRef(null);
return <Son ref={myRef}></Son>
}
function Son({ref}){
return <input ref={ref}></div>
}
// Parent组件的myRef控制内容input组件的dom元素
注意: 可以通过props
传递ref
来实现控制其他组件的dom
,ref
一般使用场景为管理焦点、滚动位置或调用 React 未暴露的浏览器 API ,避免手动修改dom
导致与React
的更改进行冲突
Effect
基本使用
Effect
的执行逻辑是在渲染结束后运行,就是先渲染页面更新,然后且得看依赖数组再判断是否在执行Effect
代码
useEffect
接收两个参数
- 执行逻辑的函数,当函数有返回时(每次
Effect
重新运行之前调用,并在组件卸载/被移除时最后一次调用) - 依赖数组(只有当你指定的 所有 依赖项的值都与上一次渲染时完全相同,
React
才会跳过重新运行该Effect
)。
对应的执行场景
scss
useEffect(() => {
// 这里的代码会在每次渲染后运行
});
useEffect(() => {
// 这里的代码只会在组件挂载(首次出现)时运行
}, []);
useEffect(() => {
// 这里的代码不但会在组件挂载时运行,而且当 a 或 b 的值自上次渲染后发生变化后也会运行
}, [a, b]);
useEffect(() => {
return ()=>{}; // Effect重新运行之前执行,和组件写在/被移出时调用
}, []);
以下是要避免的场景:state
和Effect
相互触发
scss
// 如下代码造成死循环现象
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
上述代码会导致死循环是因为Effect
运行、更新 state
、触发重新渲染、于是又触发 Effect
运行、再次更新 state
,继而再次触发重新渲染。如此反复,从而陷入死循环
结论: 只有指定依赖数组后,才会避免死循环
Effect执行的次数2次?
在开发环境下,有严格模式的约束下,会通过重新挂载你的组件,React 验证了离开页面再返回不会导致代码出错,所以导致会执行两次逻辑。
在正式环境就不用担心有这个问题,只会执行一次。
生命周期
每个 React 组件都经历相同的生命周期:
- 当组件被添加到屏幕上时,它会进行组件的 挂载。
- 当组件接收到新的
props
或state
时,通常是作为对交互的响应,它会进行组件的 更新。 - 当组件从屏幕上移除时,它会进行组件的 卸载。
javascript
useEffect(() => {
console.log(`装载 ${roomId}`);
return () => console.log(`卸载 ${roomId}`);
}, [roomId]);
上述代码在执行声明周期所执行挂载,更新,卸载的过程是如下
javascript
// 组件的首次挂载执行更新逻辑
console.log(`装载 ${roomId}`);
//组件更新,执行清理函数,在重新执行更新逻辑
console.log(`卸载 ${roomId}`)
console.log(`装载 ${roomId}`);
// 组件卸载,执行清理函数
console.log(`卸载 ${roomId}`)
Hooks
注意: Hooks只能在组件或自定义 Hook 的最顶层调用。不能在循环语句、条件语句或 map()
函数中调用