React
类组件,函数组件
// 类组件
class ArticleList extends React.Component {
render() {//渲染React元素
const { articles } = this.props;//props由父组件传入,所有 React 组件都必须像纯函数一样保护它们的 props 不被更改
return articles.map((item) => {
const { title } = item;
return <div> {title}</div>;
});
}
}
//函数组件
const ArticleList = (props) => {
const { articles } = props;
return articles.map((item) => {
const { title } = item;
return <div> {title}</div>;//注意渲染列表时要设置key属性
});
const articles = [
{ id: 1, title: 'Article 1' },
{ id: 2, title: 'Article 2' },
{ id: 3, title: 'Article 3' },
];
function App() {
return <ArticleList/*组件*/ articles/*属性,和子组件接受props的变量名一致*/={articles/*变量*/} />;
}
1.index.js调用render()函数;
2.React调用App组件;
3.App组件将变量articles作为props传入ArticleList组件;
4.ArticleList组件将div结构作为返回值;
5.React DOM更新页面
组件内部可变状态state
export default class Clock extends Component {
constructor(props) {
super(props);
this.state = { date: new Date() };//构造函数,为state赋初值
}
componentDidMount() {//在组件已经被渲染到 DOM 中后运行
this.timerID = setInterval(() => this.tick(), 1000);//定时器,每秒更新UI//挂载
}
componentWillUnmount() {
clearInterval(this.timerID);//卸载
}
tick() {
this.setState({//更新组件state
date: new Date(),
});
}
render() {
return (
<div>
<h1>Current Time: {this.state.date.toLocaleTimeString()}.</h1>
</div>
);
}
}
1.调用Clock的构造函数,初始化this.state
2.调用组件的render方法,更新DOM
3.调用componentDidMount()钩子函数,每秒调用一次tick()方法
4.tick()方法中调用setState(),重新render(),这时this.state.date已经改变
5.组件被移除时,调用componentWillUnmount(),计时器停止
受控组件
渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。
constructor(props){
super(props);
this.state = {
title: '',
content: ''
}
}
handleChange = (e) => {//输入
if (e.target.name === 'title') {
this.setState({
title: e.target.value
})
} else if (e.target.name === 'content') {
this.setState({
content: e.target.value
})
}
};
handleSave = async () => {//保存
const {title, content} = this.state;
if (title.length > 0 && content.length > 0) {
console.log(title, content)
}
};
render() {
const {title, content} = this.props;
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="title">Post Title:</label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={this.handleChange}
/>
<label htmlFor="content">Content:</label>
<textarea
id="content"
name="content"
value={content}
onChange={this.handleChange}
/>
<button type="button" className="button" onClick={this.handleSave}>
Save Post
</button>
</form>
</section>
);
}
非受控组件
表单数据将交由 DOM 节点来处理,组件通过使用ref来获取表单数据
class AddPostForm extends React.Component{
constructor(props){
super(props);
this.titleInput = React.createRef();//创建ref
this.contentInput = React.createRef();
}
handleSave = async () => {
const title = this.titleInput.current.value;
const content = this.contentInput.current.value;
if (title.length > 0 && content.length > 0) {
console.log(title, content)
}
};
render() {
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="title">Post Title:</label>
<input
type="text"
id="title"
name="title"
ref={this.titleInput}//构造函数中定义的ref
/>
<label htmlFor="content">Content:</label>
<textarea
id="content"
name="content"
ref={this.contentInput}
/>
<button type="button" className="button" onClick={this.handleSave}>
Save Post
</button>
</form>
</section>
);
}
};
组件的组合用法
组件可以在其输出中引用其他组件。
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;//name是由使用这个组件的时候再传入的
}
function App() {
return (
<div>
<Welcome name="Sara" />//传入name(字符串),动态传入的也可以组件,这样子实现了组件的组合使用
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
State Hook
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。新的管理状态逻辑的方式。
function AddPostForm(props){
const [title, setTitle] = useState('');//`useState` 会返回一对值:当前状态和一个让你更新它的函数,你可以在事件处理函数中或其他一些地方调用这个函数
const [content, setContent] = useState('');
const handleChange = (e) => {
if (e.target.name === 'title') {
setTitle(e.target.value)
} else if (e.target.name === 'content') {
setContent(e.target.value)
}
};
const handleSave = async () => {
if (title.length > 0 && content.length > 0) {
console.log(title, content)
}
};
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="title">Post Title:</label>
<input
type="text"
id="title"
name="title"
value={title}
onChange={handleChange}
/>
<label htmlFor="content">Content:</label>
<textarea
id="content"
name="content"
value={content}
onChange={handleChange}
/>
<button type="button" className="button" onClick={handleSave}>
Save Post
</button>
</form>
</section>
);
};
Effect Hook
const [articles, setArticles] = useState([])
useEffect(()=> {//每次组件在渲染的时候都会执行,但是内部的副作用函数不一定
const fetchArticles = async () => {//带有副作用逻辑的函数
try{
const response = await client.get('/fakeApi/posts');//通过接口动态获取文章标题
console.log(response)
setArticles(response.posts);
}catch(err){
console.error(err)
}
}
fetchArticles();
}, []/*只有数组内的值发生变化的时候函数fetchArticles才会被执行。如果是空数组,则在组件被挂载的时候函数执行一次 */)
自定义 Hooks
将组件逻辑提取到可重用的函数中
function useCurrentDate(){//自定义hook函数,以use开头,封装date状态管理
const [date, setCurrentDate] = useState(new Date());
const tick = () => {
setCurrentDate(new Date())
}
useEffect(() => {//副作用逻辑
const timer = setInterval(()=>{tick()}, 1000)
return () => {
clearInterval(timer)
}
}, [])
return date;
}
export default function Clock() {
const date = useCurrentDate();
return (
<div>
<h1>Current Time: {date.toLocaleTimeString()}.</h1>
</div>
);
}
只能在函数组件,自定义hook中调用hook
只能在函数的最顶层控制流中使用hook
React Router
<Router>
<Navbar />
<div className="App">
<Switch>
<Route exact path="/"> //当路由匹配时,渲染对应的组件。Route定义了一个路由规则
<Home />
</Route>
<Route path="/editor">
<Editor />
</Route>
<Route path="/login">
<Login />
</Route>
<Route path="/posts/:id"//id是可变的参数,通过history.push传递
<Post />
</Route>
</Switch>
</div>
</Router>
<Link to="/">Home</Link>组件link实现路由跳转
也可以通过命令式的API调用方式:history.push
useHistory:获取history对象
useParams:获取路由中的参数
Redux
Redux基础
Store:存取全局状态,通过configureStore创建store对象。store.getState()拿到store保存的状态
Action:描述状态发生的变化。就是一个普通的js对象。必须包含type属性,来描述状态变化。 payload属性代表action包含的参数。
Dispatch:发送一个action,接受一个action作为参数。
Reducer:一个函数,根据当前应用state,和接受的action,计算新的state。必须是纯函数(不能有副作用,不能直接修改state,集成immer库来达到效果)
slice:一个slice模块包含了管理一个子状态所需的所有逻辑(state,actions,reducer等) 通过createSlice创建。通过useSelector函数获取state。通过useDispatch修改state
Postslice添加文章的slice
import { createSlice } from '@reduxjs/toolkit';
const initialState = [
{ id: '1', title: 'First Post!', content: 'Hello!' },
{ id: '2', title: 'Second Post', content: 'More text' },
];
const postsSlice = createSlice({//创建一个slice
name: 'posts',
initialState,//slice的初始状态
reducers: {//用于添加文章
addPost(state, action) {
state.push(action.payload);
},
},
});
export const { addPost } = postsSlice.actions;
export default postsSlice.reducer;
全局Store
import { configureStore } from '@reduxjs/toolkit';
import posts from './postsSlice'//导入postSlice中的reducer函数
export default configureStore({//创建store对象
reducer: {
posts
},
});
Home组件的redux入口
import React from 'react';
import { useSelector } from 'react-redux';
import PostListComp from './components/PostList';//一个普通的react组件
const Home = () => {
const posts = useSelector((state) => state.posts)//获取全局的state中的posts即要添加的文章
return (//获取到的posts传递给PostListComp组件
<section>
<PostListComp posts={posts} />
</section>
);
};
export default Home;
editor组件的redux入口
import React from 'react';
import { useDispatch } from 'react-redux';
import { nanoid } from '@reduxjs/toolkit';
import { useHistory } from 'react-router-dom';
import AddPostForm from './components/AddPostForm';
import { addPost } from '../../states/postsSlice';
const AddPost = () => {
const dispatch = useDispatch();//修改state
const history = useHistory();
const handleSubmit = (data) => {//提交事件的处理函数
dispatch(
addPost({
...data,
id: nanoid(),
}),
);
history.push('/');
};
return <AddPostForm onSubmit={handleSubmit} />;
};
export default AddPost;
Redux异步
thunk:延迟执行的一段代码
createAsyncThunk:创建异步执行的api
const initialState = {
posts: [],//文章列表
status: 'idle',//请求的状态
error: null,
};
export const fetchPosts = createAsyncThunk('posts/fetchPosts', async () => {//(action的type,异步处理函数)
const response = await client.get('/fakeApi/posts');//通过接口获取文章列表
return response.posts;
});
export const addPost = createAsyncThunk(
'posts/addPost',
async (initialPost) => {
const response = await client.post('/fakeApi/posts', { post: initialPost });
return response.post;
},
);
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
// addPost(state, action) {
// state.posts.push(action.payload);
// },
},
extraReducers: {//因为fetchPosts是在postsSlice外部定义的,所以使用extraReducers而不是reducers
[fetchPosts.pending]: (state, action) => {//有一个fetchPosts请求开始发送了
state.status = 'loading';
},
[fetchPosts.fulfilled]: (state, action) => {//请求成功了
state.status = 'succeeded';
state.posts = action.payload;
},
[fetchPosts.rejected]: (state, action) => {//请求失败了
state.status = 'failed';
state.error = action.error.message;
},
[addPost.fulfilled]: (state, action) => {
state.status = 'idle'
},
},
});
并不是所有的状态都需要维护在redux中,比如错误信息,loading等等都是在组件层面进行关注的,redux只有一个异步action
const AddPost2 = () => {
const dispatch = useDispatch();
const history = useHistory();
const [addRequestStatus, setAddRequestStatus] = useState('idle');//组件内单独创建了一个状态,用来做loading
const [error, setError] = useState(null);
const handleSubmit = async (data) => {
try {
setAddRequestStatus('pending');
const resultAction = await dispatch(
addPost({
...data,
id: nanoid(),
}),
);
unwrapResult(resultAction);//对resultAction进行拆箱操作,看是否成功
setAddRequestStatus('idle');
history.push('/');//跳转回首页
} catch (err) {
setError(err.message);
setAddRequestStatus('idle');
}
};
return (
<section>
{addRequestStatus === 'pending' && <div className="loader">Loading...</div>}
{error && <div className="error">{error}</div>}
<AddPostForm onSubmit={handleSubmit} />
</section>
)
};
--文章详情页模块
文章详情页的state放在哪?面向feature拆分slice
const initialState = {
data: null,
status: 'idle',
error: null,
};
export const fetchPost = createAsyncThunk('post/fetchPost', async (id) => {//获取文章详情
const response = await client.get(`/fakeApi/posts/${id}`);
return response.post;
});
export const addPostReaction = createAsyncThunk(
'post/addPostReaction',
async ({ id, reaction }) => {
const response = await client.post(`/fakeApi/posts/${id}/reaction`, {
reaction,
});
return response.post;
},
);
const postSlice = createSlice({
name: 'post',
initialState,
reducers: {
clearPost(state, action) {
return initialState;
},
},
extraReducers: {
[fetchPost.pending]: (state, action) => {
state.status = 'loading';
},
[fetchPost.fulfilled]: (state, action) => {
state.status = 'succeeded';
state.data = action.payload;
},
[fetchPost.rejected]: (state, action) => {
state.status = 'failed';
state.error = action.error.message;
},
[addPostReaction.fulfilled]: (state, action) => {
state.data = action.payload;
},
},
});
其他状态管理方案
RTK Query
Recoil
Context + Hooks