js 内嵌入 的 html 标签 就是 jsx 语法。 html 标签内嵌入 js。 jsx 内写 js 直接使用 {}。 jsx 内标签的属性有几个需要特殊记忆 1. class 写成 className 2. lable 内的 for 写成 htmlFor 3. 自定义属性名写成小驼峰 4. 标签内无内容写成单闭合
render() {
// render函数默认会触发
// 组件的this上会接收父组件传递的props,props为只读的不可更改
const { text } = this.props;
return <button onClick={this.handleClick}>{text}</button>;
}
export default function Button(props) {
const { text } = props;
return <button>{text}</button>;
}
<button onClick={login_test}>事件绑定测试</button>
不能将 login_test 当作事件函数,写一个新的函数,函数内执行 login_test 并传递参数,新的函数是事件函数
<button
onClick={() => {
login_test("哈哈哈哈");
}}
>
事件传参
</button>
类内创建的公共方法就可以当作函数的使用,比如事件函数
export default class Button extends Component {
handleClick = () => {
console.log("我是按钮点击事件");
};
render() {
const { text } = this.props;
return <button onClick={this.handleClick}>{text}</button>;
<button
onClick={(e) => {
login_test("哈哈哈哈", e);
}}
>
事件传参
</button>
export default class Button extends Component {
handleClick = () => {
console.log("我是按钮点击事件");
console.log(this.props);
};
}
<button onClick={this.handleClick.bind(this)}>{text}</button>
状态变页面变,页面变化需要 state 控制
修改 state 不能直接修改 需要使用 setState 方法修改 这个方法是 Component 类自带的,所以所有组件都可以使用
使用 setState 的时候传递一个对象,对象内的属性写的就是要修改的具体的 state
切记不能直接修改state
setState 何时同步何时异步?
由 React 控制的事件处理程序,以及生命周期函数调用 setState 不会同步更新 state 。
React 控制之外的事件中调用 setState 是同步更新的。比如原生 js 绑定的事件,setTimeout/setInterval/async await 等。
更改后想要获取新的值,加一个定时器
state = {
count: 100,
};
// count++
this.setState({
count: this.state.count + 1, //不能写++ ++是更改并赋值操作直接就改了state
});
state = {
user: {
userName: "xm",
userAge: 18,
},
};
changeAge = () => {
/* this.setState({
user: {
userName: "xm",
userAge: 20,
},
}); */
this.setState({
user: { ...this.state.user, userAge: 20 },
});
};
<div className="box" style={{ display: show ? "block" : "none" }}></div>
{
show ? <div className="box"></div> : "";
}
// 当在 html 内写 undefined null '' false 的时候不会渲染任何内容
{
show && <div className="box"></div>;
}
state = {
arr: [1, 2, 3, 4, 5],
};
const { count, show, arr } = this.state;
<ul>
{arr.map((el) => (
<li key={el}>{el}</li>
))}
</ul>;
发请求拿数据
state = {
username: "xy",
};
changeForm = (e) => {
this.setState({
// 根据name属性去更改表单的值
[e.target.name]: e.target.value,
});
};
<input
name="username"
type="text"
value={username}
onChange={this.changeForm}
/>;
state= {
likes: ["vue", "react"],
}
// 更改多选
changeLikes = (e) => {
const target = e.target;
this.setState({
// 如果为true,在likes中追加这个元素的value,为false,likes中去除这个元素
likes: target.checked
? [...this.state.likes, target.value]
: this.state.likes.filter((el) => el !== target.value),
});
};
<h4>你喜欢的框架有哪些</h4>
<label htmlFor="">vue</label>
<input type="checkbox" value="vue" checked={likes.includes("vue")}
onChange={this.changeLikes} />
<br />
<label htmlFor="">react</label>
<input type="checkbox" value="react" checked={likes.includes("react")}
onChange={this.changeLikes} />
<br />
<label htmlFor="">angular</label>
<input type="checkbox" value="angular" checked={likes.includes("angular")}
onChange={this.changeLikes} />
<br />
<label htmlFor="">微信小程序</label>
<input type="checkbox" value="mini" checked={likes.includes("mini")}
onChange={this.changeLikes} />
<br />
import { Component, createRef } from "react";
checkBox = createRef();
<input type="checkbox" defaultChecked={true} ref={this.checkBox} />;
// 获取组件实例
checkBox.current;
__html
属性是 html 字符串的话会被解析到该元素内<div dangerouslySetInnerHTML={{ __html: content }}></div>
当父组件传递的内容在子组件双标签之间的话,那么子组件或使用 children prop 来接收
// children prop 存储的是父组件在使用子组件的时候标签之前传递的html内容
// 当内容是一个节点(react.element)的话,那么children指的就是这个节点
// 当内容是多个节点(react.element)的话,children是个数组,存储这些节点
// 因为 react 可以直接写 jsx 语法,所以children prop可以直接传递html标签
// 那么多个节点处理的时候 children用起来就没有 prop方便了
const { children, dialogContent } = this.props;
组件间的交互,将公共的状态提升就是用户对子组件操作,子组件不改变自己的状态,通过自己的 props 把这个操作改变的数据传递给父组件,改变父组件的状态,从而改变受父组件控制的所有子组件的状态,这也是 React 单项数据流的特性决定的。官方的原话是:共享 state(状态) 是通过将其移动到需要它的组件的最接近的共同祖先组件来实现的。 这被称为“状态提升(Lifting State Up)”。
就是在使用组件的时候 给组件传递特定的 props 组件才会展示对应的内容,那么如何约束父组件传递的 props,就是 propTypes 类型检查
import PropTypes from "prop-types"
; PropTypes 这个包来处理类型检查,这个包已经默认下载好了,无需自己下载PropTypesDemo.propTypes = {
title: PropTypes.string,
description: PropTypes.string.isRequired, //必传
};
PropTypesDemo.defaultProps = {
title: "默认标题",
};
context 用于父元素向后代元素传递数据
import { createContext } from 'react'
state = {
themeColor: "blue",
};
changeColor = (color) => {
this.setState({
themeColor: color
})
// changeColor为更改属性函数
<Provider value={{ themeColor, changeColor: this.changeColor }}>
{/* 利用 Provider 组件将 value 的值传递给 son 组件以及后代组件 */}
<Son />
</Provider>;
import { Consumer } from './Parent'
// Consumer 是消费者, 给 Consumer 组件传递 函数 Children ,那么该函数的参数就是 Provider 提供的 value, 该函数返回 节点
<Consumer>
{({ themeColor, changeColor }) => (
<div>
<h3 style={{ color: themeColor }}>我是子组件</h3>
<Grandson />
</div>
)}
</Consumer>
npm i node-sass sass-loader
npm run eject
npm i less less-loader
npm i styled-components
一种在函数组件内替代类组件内的 state 生命周期等的技术。
import {useState} from react
const [num,setNum] = useState(100)
// 执行顺序 初始化 useEffect触发了➡useEffect 的参数函数的返回值触发
// 更新时候 useEffect 的参数函数的返回值触发 ➡ useEffect触发了
useEffect(() => {
console.log("useEffect触发了");
return () => {
console.log("useEffect 的参数函数的返回值触发");
};
});
useEffect(() => {
// 函数组件函数本身就相当于 render 生命周期,假如不在生命周期中用定时器,页面会一直重载
// useEffect 内使用 setInterval 需要每次更新的时候销毁 setInterval 并生成新的setInterval,就是每次更新的时候 setInterval 只执行一次,然后生成新的 setInterval
// 当useEffect 内使用了 useEffect 外的变量时会出问题
const timer = setInterval(() => {
// setInterval可以替换成 setTimeout
setNum(num + 1);
}, 1000);
return () => {
if (timer) {
clearInterval(timer);
}
console.log("useEffect 的参数函数的返回值触发");
};
});
useContext 其实就是简化了 Consumer 的写法
import { createContext, useState } from "react";
export const Theme = createContext("red")
export default function HookDemo2() {
const [themeColor, setTehemeColor] = useState("red");
return (
<div>
<h3>我是父组件{themeColor}</h3>
<Theme.Provider value={{ themeColor, setTehemeColor }}>
<HookDemo22 />
</Theme.Provider>
</div>
);
}
import { Theme } from "./HookDemo2"; import { useContext } from "react";
export default function HookDemo22() {
// useContext其实就是简化了 Consumer 的写法
const { themeColor, setTehemeColor } = useContext(Theme);
return (
<div>
<h4>我是子组件{themeColor}</h4>
<button onClick={() => setTehemeColor("pink")}>修改颜色</button>
</div>
);
}
import { useState, useRef, useEffect } from "react";
export default function HookDemo4() {
const [num, setNum] = useState(0);
const numRef = useRef(num);
numRef.current = num;
const add = () => {
setNum(num + 1);
};
useEffect(() => {
// 在合并没有获取到最新的 num 而是获取的最初的 num
// 因为在 useEffect 内创建了异步函数(setTimeout setInterval Promise 等),
// 那么就会形成闭包,内部函数内的变量获取的一直是最外层最初的值
// 因此利用useRef的不变性,可以解决这个问题
setTimeout(() => {
console.log("3秒后的结果为" + numRef.current);
}, 3000);
}, []);
return (
<div>
<p>{num}</p>
<button onClick={add}>num++</button>
</div>
);
}
const getTotal = () => {
console.log("total开始计算");
return goods.reduce((res, ele) => (res += ele.goodsPrice), 0).toFixed(2);
};
const total = useMemo(getTotal, [goods]);
就是将 hook 的逻辑封装到一个函数中,这个函数就被称为 hook 函数。
当多个组件需要用到同一种 hook 逻辑时,需要将逻辑分离成自定义 hook 然后导入使用
npm i react-router-dom
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById("root")
);
Route标签
<Routes>
<Route path="/" element={<Home />}>
<Route element={<Post />} path="/post/:postId"></Route>
</Route>
<Route path="about" element={<About />}>
<Route path="cart" element={<Cart />}></Route>
<Route path="store" element={<Store />}></Route>
</Route>
</Routes>
<NavLink to="/" className={({ isActive }) => (isActive ? "active" : "")} >
Outlet
标签<Route path="about" element={<About />}>
<Route path="cart" element={<Cart />}></Route>
<Route path="store" element={<Store />}></Route>
</Route>
<Route element={<Post />} path="/post/:postId"></Route>
import { useParams } from "react-router-dom";
const { postId } = useParams();
<Route index element={<Cart />}></Route>
{
/* replace:true替换历史记录 */
}
<button onClick={() => navigate("/", { replace: true })}>回到首页</button>;
<button onClick={() => navigate(-1)}>返回</button>;
利用 useRoutes 来设置路由
// 创建一个单独的 routes.js文件
import { lazy, Suspense } from "react";
import About from "./views/About";
import Cart from "./views/Cart";
import Home from "./views/Home";
import Store from "./views/Store";
// 懒加载
const Post = lazy(() => import("./views/Post"));
const routers = [
{
path: "/",
element: <Home />,
children: [
{
// 默认子路由
index: true,
element: <h3>hahaha</h3>,
},
{
path: "post/:postId",
// 当需要渲染 Post 的组件的时候会先渲染 fallback的内容,直到Post组件加载完毕
element: (
<Suspense fallback={null}>
<Post />
</Suspense>
),
},
],
},
{
path: "about",
element: <About />,
children: [
{
path: "store",
element: <Store />,
},
{
path: "cart",
element: <Cart />,
},
],
},
];
export default routers;
// App 组件中
import routes from "./routes";
function App() {
const router = useRoutes(routes);
}
import { lazy, Suspense } from "react";
const Post = lazy(() => import("./views/Post"));
const routers = [
{
path: "post/:postId",
// 当需要渲染 Post 的组件的时候会先渲染 fallback的内容,直到Post组件加载完毕
element: (
<Suspense fallback={null}>
<Post />
</Suspense>
),
},
];
const routers = [
{
path: "/",
element: (
<RequireAuth>
<Home />
</RequireAuth>
),
},
];
import { message } from "antd";
import { useEffect } from "react";
import { Navigate } from "react-router-dom";
const NavigateTip = () => {
useEffect(() => {
message.error("没有登录请登录");
});
return <Navigate to="/login" replace />;
};
export default function RequireAuth({ children }) {
const info = sessionStorage.getItem("info");
return info ? children : <NavigateTip />;
}
独立的状态管理工具。在 react 中使用的话,还需要借助 react-redux(或者 Redux Toolkit)使用
安装 redux npm i redux
安装 react-redux npm i react-redux
创建 createStore(reducer)
// creatStore是初始化 store 的方法,传递 reducer函数 作为参数,createStore 执行的时候自动 reducer 函数,并且将reducer函数的返回值制作成 store 数据
const store = createStore(rootReducer, enhancer);
export default store;
// reducer函数
// 需要定义两个参数,第一个参数state可用来初始化store数据
// 第二个参数 action 是一个对象存储了type属性。这个action是一个行为名称
// 这个函数必须设置返回值,返回的值是 store 的数据,默认要返回第一个参数
const rootReducer = (state = { counter: 0 }, action) => {
const { type } = action;
switch (type) {
case "increment":
return {
counter: state.counter + 1,
};
default:
return state;
}
};
使用 需要使用 react-redux
先用 Provider 提供 store 给 App 组件
import { Provider } from "react-redux";
import store from "./store/index";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
在组件中使用 connect 或者 useSelector 动态获取 store 数据
import { useSelector, useDispatch } from "react-redux";
const counter = useSelector((state) => state.counter);
import "./App.css";
import store from "./store/index";
import { connect } from "react-redux";
function App({ counter }) {
return (
<div className="App">
<h3>{counter}</h3>
</div>
);
}
const mapStateToProps = (state) => ({
counter: state.counter,
});
export default connect(mapStateToProps)(App);
修改的话使用 store.dispatch 发对应 action 或者直接使用 useDispatch 获取 dispatch 来发 action
import { useSelector, useDispatch } from "react-redux";
const dispatch = useDispatch();
dispatch({ type: "increment" });
处理异步操作,需要使用 redux-thunk
npm i redux-thunk
export const initial = () => (dispatch) => {
setTimeout(() => {
dispatch({
type: "initial",
newCounter: 100,
});
}, 1000);
};
Redux 期望所有状态的更新都是不可变性的
npm install @reduxjs/toolkit
或者 npx create-react-app my-app --template redux
创建一个模板// 数据初始值
const initialState = {
value: 0,
};
// 创建切片
const counterSlice = createSlice({
name: "counter",
initialState,
// reducers 动作处理 写法可以对比之前的 switch 判断,写成对象
reducers: {
increment: (state) => {
state.value++;
},
},
});
// 导出action
export const [increment] = createSlice.actions;
// 导出切片
export default counterSlice;
import { configureStore } from "@reduxjs/toolkit";
import counter from "./slices/counter";
export const store = configureStore({
reducer: {
[todos.name]: todos.reducer,
},
});
// 要处理异步的action可以直接创建
// 异步action内层函数接收dispatch以及getState函数作为参数
// dispatch用来发action
// getState用于获取state数据
export const fetchCounter = () => (dispatch, getState) => {
console.log(getState());
setTimeout(() => {
dispatch(change(10));
}, 1000);
};
好处: dispatch 调用后可以加 then
// 利用 createAsyncThunk 创建函数
// createAsyncThunk 返回值是一个 promise
// 当组件dispatch(函数()) promise 执行
// 执行的时候 extraReducers 会监听 promise的状态,去做对应的处理
// createAsyncThunk的第二个参数函数的返回值会被当作 action 的payload
export const fetchCounter = createAsyncThunk(
"counter/fetchCounter",
async () => {
const res = await new Promise((resolve, rejected) => {
setTimeout(() => {
resolve(1000);
}, 1000);
});
return res;
}
);
// 在切片中使用 extraReducers配置项
const counterSlice = createSlice({
name: "counter",
initialState,
reducers: {
increment: (state) => {
state.value++;
},
change: (state, action) => {
state.value = action.payload;
},
},
// extraReducers用来处理 createAsyncThunk 创建的异步thunk
// fulfilled是异步的状态 pending rejected
extraReducers: (builder) => {
builder.addCase(fetchCounter.fulfilled, (state, action) => {
state.value = action.payload;
});
},
});
import { createSelector } from "@reduxjs/toolkit";
const selectTodos = (state) => state.todos.value;
const selectFilter = (state) => state.filter.value;
// 根据 filter 展示todos
export const selectFilterTodos = createSelector(
selectTodos,
selectFilter,
(todos, filter) => {
return todos.filter((todo) =>
filter === "all"
? true
: filter === "active"
? !todo.isComputed
: todo.isComputed
);
}
);