一、react类组件
setState
-
setState(updater, [callback])
-
updater: 更新数据 FUNCTION/OBJECT
-
callback: 更新成功后的回调 FUNCTION
-
异步: react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能
-
浅合并 Objecr.assign(),通过setState 修改状态时,只需要传入要修改的state即可,setState 会帮助我们进行浅合并,将 修改sttae 合并入 组件的state 中。
-
调用 setState 之后,会触发生命周期,重新渲染组件
注意事项
-
如果 state 中的数据,是一个引用类型,要修改它,一定要记得返回一个新的引用
addTodo=(newTodo)=>{ const {data} = this.state; // 在 React 不要直接修改状态 /* data 是一个引用类型,这里直接修改 data 相对于修改 this.state.data
在 React 直接修改 this.state,在更新之后,就没有办法获取到更新之前的状态 */let newData = [...data,{ id: Date.now(), todo: newTodo, done: false }]; // data.push({ // id: Date.now(), // todo: newTodo, // done: false // }); // 调用 setState 完成对 data 的修改 this.setState({ data:newData }); }
组件间通信
- 在 React.js 中,数据是从上自下流动(传递)的,也就是一个父组件可以把它的 state / props 通过 props 传递给它的子组件,但是子组件不能修改 props - React.js 是单向数据流,如果子组件需要修改父组件状态(数据),是通过回调函数方式来完成的。
父级向子级通信
- 把数据添加子组件的属性中,然后子组件中从props属性中,获取父级传递过来的数据
子级向父级通信
- 在父级中定义相关的数据操作方法(或其他回调), 把该方法传递给子级,在子级中调用该方法父级传递消息
跨组件通信
-
|
// App.js中引入child.js,将 value 值传到 subschild.js 孙子组件中 return <> <Provider value={{ count, name, setCount:this.setCount }} >
</>// 子组件child.js return
数据修改
{/* 旧做法,一层层往下传 /} {/ /} {/ <SubChild {...this.props} /> */}import { Component } from "react"; import context, { Consumer } from "./context"; class SubChild extends Component { render() { // const {count,name} = this.props; // console.log(this.props); return {(context) => { const { count, name } = context; return
}} } }count:{count}
name:{name}
export default SubChild;
-
|
contextimport { Component } from "react"; import context, { Consumer } from "./context"; class SubChild extends Component { static contextType = context;// contextType ,告诉当前组件,当前组件中 context 值,从哪个 context 中获取。 render() { const { count, name, setCount } = this.context; return
} }count:{ count}
name:{name }
<button onClick={()=>{ setCount(count +1); }}>递增export default SubChild;
受控组件与非受控组件
-
受控组件: 想要获取表单的一些内部状态时,就可以将表单的内部状态和组件的状态进行绑定,这样就形成受控组件
-
checked:将 组件的状态,赋值给控件的 checked 属性,通过 onChange 事件监听控件的 checked 属性发生变化,再赋值给 组件的 state
-
value: 将 组件的状态,赋值给控件的 value 属性,通过 onChange 事件监听控件的 value 属性发生变化,再赋值给 组件的 state
-
-
非受控组件:
-
需要设置初始值,只设置初始值,并不希望该控件受控,不要用value|checked(只要用 value|checked)react 就认为我们做受控组件. 这里要用 defaultValue 和 defaultChecked
-
不需要设置初始值
-
生命周期函数
mount 挂载阶段 --- 组件从初始化到真实渲染到DOM中
-
constructor 组件初始化
-
static getDerivedStateFromProps(props)
-
注意 this 问题
-
render
-
componentDidMount -- 处理副作用(请求)
update 更新阶段
(从setState之后组件开始更新,一直完成对真实的DOM节点更新)
-
static getDerivedStateFromProps(props, state)
-
shouldComponentUpdate() -- 判断是否更新
-
render()
-
getSnapshotBeforeUpdate()
-
componentDidUpdate() -- 处理副作用(请求)
unmount 卸载阶段
- componentWillUnmount 组件即将卸载
副作用:
-
获取真实 DOM 节点
-
数据请求
常用的三个生命周期函数:
shouleComponentUpdate()
用于判断组件更新与否,优化性能
shouldComponentUpdate(nextProps,nextState){
//nextProps 更新后的props, nextState 更新后的state
//console.log(nextState,this.state);
console.log(2,"判断组件是否需要更新");
return true; //返回值为 true 则继续执行更新,返回值为false 则不执行更新,后续的生命周期函数,也不会执行
}
componentDidUpdate()
组件更完成更新,比对更新前后数据差异做相应的逻辑处理
componentDidUpdate(prevProps,prevState,prevDOM){
// console.log(prevDOM);
console.log(5,"组件更新完成");
}
componentDidMount()
componentDidMount(){
console.log(3,"组件挂载完成,虚拟DOM,已经生成了真实DOM");
}
children API
App.js
class App extends Component {
render() {
// 调用组件时,如果在标签对中间写内容,该内容会传递给子组件的props.children 属性
return <Child>
<div>明天周末了,要约一块做练习吗</div>
<button>约</button>
<button>不约,练习太少已经做完了</button>
{()=>{
alert("你想干啥")
}}
</Child>
}
}
child.js
通过 props将父组件中的元素传进
class Child extends Component {
render(){
//console.log(this.props);
this.props.children[3]();
return <div>
<h1>children</h1>
{this.props.children.slice(0,3)}
</div>
}
}
dangerouslySetInnerHTM API
用于解析含html标签的数据
import { Component } from "react";
const data = `<h2>文章标题</h2><p>段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1</p><p>段落2段落2段落2段落2段落2段落2段落2段落2段落2</p>`;
// class App extends Component {
// render() {
// return <div ref={(node)=>{
// node.innerHTML = data;
// }}>
// </div>
// }
// }
class App extends Component {
render() {
return <div
dangerouslySetInnerHTML={{
__html:data
}}
>
</div>
}
}
二、react函数式组件
介绍:
-
函数是组件,本质就是一个函数,接收参数 props 并返回 reactElement
-
函数式组件中没有 this 和生命周期函数,不能使用 string ref
-
使用函数式组件时,应该尽量减少在函数中声明子函数,否则,组件每次更新都会重新创建该子函数
ReactHooks:
常用 hook
useState
const [state, setState] = useState(initialState);
const [状态,修改该状态的方法] = useState(初始值);
hooks 使用注意事项:
1. hook 必须在组件的最外层调用,不能包含在 if for 或 子函数 等等
内置 hook
useState 定义状态
`const [state, setState] = useState(initialState);`
let [状态,修改该状态的方法] = useState(初始值);
1. 在同一个组件中可以使用 useState 定义多个状态
2. 注意 useState 返回的 setState 方法,不会进行对象合并
3. 注意 useState 返回的 setState 方法同样是异步方法
/*
函数式组件在更新时,是整个函数重新执行一遍
*/
/*
mount阶段:
rows1: const [count,setCount] = useState(0)
let state = [0];
return [state[0],(nub)=>{state[0] = nub; updater()}]
rows2:const [name,setName] = useState("");
state.push(""); ---> state=[0,""]
return [state[1],(data)=>{state[1] = data; updater()}]
updater阶段:
rows1: const [count,setCount] = useState(0);
return [state[0],(nub)=>{state[0] = nub; updater()}]
*/
挂载阶段和更新阶段原理图类似
useEffect
useEffect:副作用 hook,用于替代生命周期
useEffect(function{ ---> effect 函数
return ()=>{ ---> 返还函数
}
},[依赖参数])
*/
/*
组件挂载阶段:
1. 执行 useEffect 将 effect 函数存入队列
2. 挂载完成之后,执行 effect 函数队列,并获取返回函数存入队列
组件更新阶段:
1. 执行 useEffect 将 effect 函数存入队列
2. 更新完成之后,先将返回函数队列执行: 在执行时会观察该 effect 是否有依赖参数,无依赖数据,直接执行,有依赖则追踪依赖是否改变,改变才执行,不变则不执行
3. 执行新的 effect 函数存入队列: 在执行时会观察该 effect 是否有依赖参数,无依赖数据,直接执行,有依赖则追踪依赖是否改变,改变才执行,不变则不执行
卸载阶段:
1. 将返回函数队列执行
useEffect()执行过程以及参数设置:
// 没有依赖数据,挂载、更新、卸载阶段都执行
useEffect(()=>{
console.log("effect-1");
return ()=>{
console.log("effect 返还函数 - 1");
}
})
// 挂载和卸载阶段执行
useEffect(()=>{
console.log("effect-2");
return ()=>{
console.log("effect 返还函数 - 2");
}
},[])
// 有依赖数据,数据更新时候,更新执行,其他俩个都执行
useEffect(()=>{
console.log("effect-3");
return ()=>{
console.log("effect 返还函数 - 3");
}
},[count])
useEffect()用于替代生命周期函数版本一:
/*
useEffect 作用:副作用函数用来替代生命周期
componentDidMount、componentDidUpdate 和 componentWillUnmount
*/
// 挂载时 和 更新时 都执行
function Child(){
const [count,setCount] = useState(0);
const [name,setName] = useState("a");
// 组件挂载时和count有变化时执行
useEffect(()=>{
console.log("请求数据","组件挂载时和count有变化时执行")
},[count]);
// 只在组件挂载阶段执行
useEffect(()=>{
console.log("只在挂载阶段执行");
return ()=>{
console.log("组件的即将卸载");
}
},[])
useEffect()用于替代生命周期函数版本二:
/*
useEffect 作用:副作用函数用来替代生命周期
componentDidMount、componentDidUpdate 和 componentWillUnmount
*/
// 挂载时 和 更新时 都执行
function Child(){
const [count,setCount] = useState(0);
const [name,setName] = useState("a");
const isMount = useRef(true);
// 组件挂载时和count有变化时执行
useEffect(()=>{
console.log("请求数据","组件挂载时和count有变化时执行")
},[count]);
// 只在组件挂载阶段执行 componentDidMount()
useEffect(()=>{
console.log("只在挂载阶段执行");
// componentWillUnmount()
return ()=>{ // 只在组件卸载的时候
console.log("组件的即将卸载");
}
},[]);
// 当组件更新时 ComponentDidUpdate()
useEffect(()=>{
if(!isMount.current){
console.log("组件更新")
} else {
isMount.current = false;
}
})
useRef
-
useEffect 类组件 componentDidMount、componentDidUpdate 和 componentWillUnmount 需要清除的副作用
-
useRef 用户关联原生DOM节点,或者用来记录组件更新前的一些数据
const prevCount = useRef(count); // 当 Ref 中保存的是数据时,数据并不会随着组件的更新自动更新 useEffect(()=>{ console.log(prevCount.current); prevCount.current = count; })
React Hooks 优势
-
简化组件逻辑
-
复用状态逻辑
-
无需使用类组件编写
Hook 使用规则
-
只在 React 函数中调用 Hook
-
React 函数组件中
-
React 自定义 Hook 中
-
-
只在最顶层使用 Hook
自定义hook
import { useEffect, useState } from "react";
function useScroll() {
const [Y,setY] = useState(0);
useEffect(()=>{
// 初始Y
setY(window.scrollY);
window.onscroll = ()=>{
setY(window.scrollY);
};
return ()=>{
window.onscroll = null;
}
},[]);
return [Y,(Y)=>{
window.scrollTo(0,Y);
// 更新Y值
setY(Y);
}];
}
export {useScroll};
三、react-route
组件
BrowserRouter 组件 -- history
基于 HTML5 History API 的路由组件
HashRouter 组件 -- hash
基于 URL Hash 的路由组件
/*
设置路由模式:history(BrowserRouter) 、hash(HashRouter)
*/
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter> ,
document.getElementById('root')
);
Route 组件
Route使用方式(Switch | Redirect)
Route 知识点
-
path:该路由要匹配的路径
-
匹配模式:
-
默认:默认情况 path 是 模糊匹配:url 以 path 为开始时 则匹配成功
-
exact: 精确匹配: url 为 path 或 path/ 时匹配
-
strict 严格匹配: url === path 可以匹配,设置严格匹配时必须先设置 exact
-
-
多匹配路径
-
component: 路径匹配成功之后,要显示的视图
-
render:和component起着类似的作用,但可以做一些视图显示前的判断处理
<Route path="/about" exact render={(routerProps)=>{ //console.log(routerProps); //render不会将参数自动传递,需要手动传入 return <AboutPage {...routerProps} user={user} /> }} />
Link | NavLink
NavLink:
-
NavLink 作用跟 Link 类似,都是提供了一个视图跳转链接
-
NavLink 可以给当前选中项,添加选中效果
-
NavLink 在匹配当前项时,默认也是 模糊匹配
-
activeClassName:当前选中项的class,默认为 active
-
activeStyle
-
isActive,isActive 接收一个回调函数,通过该回调函数的返回值,决定项当前要不要选中,返回值 为 true 则 选中当前项,false 不选中
-
Switch
常用来匹配其他非法路径(404页面的应用)
Redirect
重定向
App.js
function App() {
return <div className="wrap">
<Nav />
<hr />
<Switch>
<Route path={["/","/home","/index"]} exact strict component={IndexPage} />
<Route path="/about" exact component={AboutPage} />
<Route path="/about/details" component={AboutDetailsPage} />
{/*
<Route path="/404" component={Page404} />
<Redirect to="/404" />
*/}
<Route path="/list/:t?/:p?" exact
render={({match})=>{
const {t="good",p="1"} = match.params;
if(types.includes(t)
&& parseInt(p)+"" === p){
return <ListPage />
}
// return <Page404 />
return <Redirect to="/404" />
}}
/>
<Route component={Page404} />
</Switch>
</div>
}
function Nav() {
return <nav>
{/* <a href="/">首页</a> */}
<Link to="/">首页</Link>
<span> | </span>
{/* <a href="/about">关于</a> */}
<Link to="/about">关于</Link>
<span> | </span>
<Link to="/about/details">关于-详情</Link>
<span> | </span>
<Link to="/about/details">列表</Link>
<span> | </span>
{/* <Link to="https://wwww.baidu.com">百度</Link> */}
<a href="https://wwww.baidu.com">百度</a>
</nav>
}
Link 组件
import { Link } from "react-router-dom";
function Nav() {
return <nav>
{/* <a href="/">首页</a> */}
<Link to="/">首页</Link>
<span> | </span>
{/* <a href="/about">关于</a> */}
<Link to="/about">关于</Link>
<span> | </span>
<Link to="/about/details">关于-详情</Link>
<span> | </span>
{/* <Link to="https://wwww.baidu.com">百度</Link> */}
<a href="https://wwww.baidu.com">百度</a>
</nav>
}
export default Nav;
四、react-redux
组件对 state 的使用或者修改
connect 高级组件
// let withRedux = connect(state=>({count:state.count})); // connect,可以接收一个类型为函数的参数,该函数作用为截取 state ,最终 withRedux 只会将截取出来的部分传递给组件。注意:该函数的返回值必须为对象
// export default withRedux(App);
export default connect((state)=>({count:state.count}))(App);
hooks组件
-
useDispatch
const dispatch = useDispatch();
-
useSelector
const todos = useSelector(state => state.todos);
-
useStore
createStore
import { createStore } from "redux";
function reducer(state={count: 1}, action) {
switch (action.type) {
case "ADD":
return {
count: state.count + 1
};
}
return state;
}
const store = createStore(reducer);
// using
store.dispatch({
type: "ADD",
});
理解 Redux 核心几个概念与它们之间的关系
-
state 状态
-
reducer 纯函数 - 提供修改状态的方法
-
纯函数:
-
相同的输入永远返回相同的输出
-
不修改函数的输入值
-
不依赖外部环境状态,只依赖于自己的参数
-
无任何副作用: DOM操作、异步程序
-
-
-
store 仓库 - 管理状态
-
dispatch: ƒ dispatch(action) 发起一个 action
-
调用 dispatch 之后,dispatch 会将通知 store 执行 reducer,并将 action 传递给 reducer
-
dispatch 是同步方法
-
-
getState: ƒ getState() 获取状态
-
replaceReducer: ƒ replaceReducer(nextReducer)
<button onClick={()=>{ store.replaceReducer((state={count:1},action)=>{ switch (action.type) { case "ADD": return { count: state.count + 2 }; //reducer 的返回值,是修改后的状态 case "MIUS": return { count: state.count - 2 } } return state; }) }}>替换reducer</button> -
subscribe: ƒ subscribe(listener)
const store = createStore(reducer); render(); let unSubscribe = store.subscribe(()=>{ render(); }); //监听state发生变化 -
action 动作 - 对 state 的修改动作
-
action 就是一个 JS 的普通对象
-
action 必须有一个 type 属性,type 属性中,描述了要对 state 做何种修改
-
潜规则:action的type 必须大写
-
reducer 的拆分与合并
import { createStore, combineReducers } from "redux";
function count(count=1, action) {
switch (action.type) {
case "COUNT_ADD":
return count + 1;
}
return count;
}
function todos(todos=[], action) {
switch(action.type) {
case "TODOS_ADD":
return [
...todos,
{
id: Date.now(),
todo: action.todo
}
]
}
return todos;
}
export default createStore(combineReducers({
count,
todos
}));
异步请求
-
异步 action
-
dispatch(action) --> reducer
-
dispatch(异步action) --> 请求数据 --> dispatch(同步)-->reducer
-
使用了 Redux Thunk 中间件之后,dispatch 可以接收一个类型为函数的 action
-
对象 action:会直接调用 reducer 修改
-
函数 action:会执行这个函数
function List() { const {data,loading} = useSelector(state=>state.list); const dispatch = useDispatch(); useEffect(()=>{ // dispatch({ // type: "LIST_LOAD", // data:[{ // id:1, // title:1 // }] // }) dispatch(function(dispatch,getState) { console.log("正在发起数据请求"); fetch('http://localhost:8080/api/topics').then(res=>res.json()) .then(res=>{
dispatch({ type: "LIST_LOAD", data:res.data }) console.log("数据请求成功"); }) }) },[]) return-
{loading ?
"数据请求中"
:data.map(item=>
- {item.title} ) }
// store.js // 处理列表状态 function list(list={ data:[], loading:true },action) { switch (action.type) { case "LIST_LOAD": return { data:action.data, loading: false } } return list; }