前言
本文主要会介绍React的主要学习过程,记录在学习的过程中遇到的知识点
一、React常见语法介绍
- 在react中事件名字使用驼峰的命名方式
二、使用function定义组件(推荐)
1.解构赋值介绍
- 解构赋值,把children单独取出来,把剩下的放在rest变量中
- 每一个组件都有一个children 表示标签内部的子节点
const Counter = ({ children, ...rest }) => {
return (
<>
<button>按钮</button>
{children}
</>
);
};
2.父组件向子组件中传参
在组件定义的时候,可以接收一个props值,表示父组件传递过来的数据
const Amount = (props) => {
console.log(props)
return (
<>
<h1>{props.msg}</h1>
<div>添加子组件</div>
</>
)
}
function App() {
return (
<div className="main">
<Amount msg="我是你爸爸" />
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
3.子组件向父组件传参
子组件使用方法调用,直接调用父组件传递的方法
const Son = ({ handle }) => {
const btnClick = () => {
handle('小白')
}
return <button onClick={btnClick}>按钮</button>
}
function App() {
const handle = (name) => {
console.log(name)
}
return (
<div className="main">
<Son handle={handle} />
</div>
)
}
4.非相关组件之间的传参
以后在写,设计到以后使用的方法
三、在function组件中
React在v16.8 的版本中推出了 React Hooks 新特性,Hook是一套工具函数的集合,它增强了函数组件的功能,hook不等于函数组件,所有的hook函数都是以use开头
1.useState
在react中定义局部组件的状态,当组件的状态或者属性信息发生改变时,组件会重新渲染
- useState接收一个参数作为初始化
- 返回一个数组作为结果
- 数组的第一项表示可以使用的变量名
- 数组的第二项表示改变数据的方法,记得改变数据只能调用这个方法,否则组件不会重新渲染
案例:使用useState完成代办事项案例
const ToDo = () => {
const { useState } = React
const [list, setList] = useState([])
return (
<>
<input
type="text"
placeholder="请输入添加的任务"
onKeyUp={addHandle}
/>
<ul>
{list.map((item, index) => (
<li key={item.id}>
{item.todoName}
<button onClick={() => delHandle(index)}>删除</button>
{item.isEnd ? null : (
<button onClick={() => finishHandle(index)}>完成</button>
)}
</li>
))}
</ul>
</>
)
// 添加函数--添加代办任务
function addHandle(evt) {
let e = evt || window.event
if (e.keyCode == 13 && e.target.value) {
setList([
...list,
{
id: Math.random() * 9999, //任务id
todoName: e.target.value, //任务名字
isEnd: false, //是否完成
},
])
e.target.value = ''
}
}
// 删除函数--根据索引删除
function delHandle(index) {
setList(list.filter((item, current) => current != index))
}
// 修改目前任务的状态
function finishHandle(index) {
setList(
list.map((item, current) => {
if (current == index) {
return {
...item,
isEnd: true,
}
} else {
return { ...item }
}
})
)
}
}
function App() {
return (
<div className="main">
<ToDo />
</div>
)
}
ReactDOM.render(<App />, document.querySelector('#app'))
2.useEffect
useEffect叫副作用,接收两个参数,参数一是回调函数,参数二是一个依赖数组
- 当数组中的数据改变的时候回调函数会重新执行
- 如果参数二为空数组,那么只有在初始化的时候执行一次
- 如果不传递参数二,那么每一次组件更新都会执行回调函数
- useEffect的参数一如果返回一个function,那么这个function在组件销毁的时候执行
- useEffect的作用是模拟组件生命周期函数
const [count, setCount] = useState(0)
useEffect(() => {
console.log('不传第二个参数')
})
useEffect(() => {
console.log('第二个参数为空数组')
}, [])
useEffect(() => {
console.log('参数为count')
}, [count])
const { useState, useEffect } = React
const Destory = () => {
useEffect(() => {
console.log('该组件创建了')
return () => {
console.log('该组件销毁了')
}
}, [])
return <h1>这个组件会被销毁</h1>
}
function App() {
const [isshow, setShow] = useState(true)
return (
<div className="main">
<button
onClick={() => {
setShow(!isshow)
}}
>
隐藏
</button>
{isshow ? <Destory /> : <h1>被销毁后显示</h1>}
</div>
)
}
3.memo
- memo的作用是对组件做缓存,每当组件的属性数据没有更新的时候组件不会重新渲染
- 建议所有的自定义组件如果可以都使用memo做一次修饰
const { useState, memo } = React;
const Item = (props) => {
console.log("组件渲染了");
return <li>{props.content}</li>;
};
const MItem = memo(Item);
function App() {
const [list, setList] = useState([]);
const keyUpHandle = (e) => {
if (e.keyCode === 13 && e.target.value) {
setList([
...list,
{
id: Date.now(),
content: e.target.value,
},
]);
}
};
return (
<div className="main">
<input type="text" placeholder="请输入内容" onKeyUp={keyUpHandle} />
<ul>
{list.map((item) => (
<MItem {...item} key={item.id} />
))}
</ul>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("app"));
4.useCallback
- 缓存一个function
- 参数一 表示function
- 参数二 表示依赖项,当依赖项改变的时候function会重新赋值
const { useState, useEffect, memo, useCallback } = React
const Count = ({ step, countHandle }) => {
console.log('组件更新了')
// 设置count的初始值为1
const [count, setCount] = useState(1)
useEffect(() => {
countHandle(count)
}, [count])
return (
<button onClick={() => setCount(count + step)}>
count:{count}--step:{step}
</button>
)
}
// 对该组件进行缓存,若数据不发生改变则不重新渲染该组件
const MCount = memo(Count)
function App() {
const [step, setStep] = useState(1)
// 这是测试用例
const countHandle = useCallback((a) => {
console.log('当前的数值为' + a)
}, [])
const [num, setNum] = useState(1)
return (
<div className="main">
<input
type="range"
step="1"
defaultValue="2"
min="1"
max="10"
style={{ width: '80%' }}
onChange={(e) => {
// console.log(e.target.value);
setStep(e.target.value * 1)
}}
/>
<br />
<MCount step={step} countHandle={countHandle} />
<br />
<button onClick={() => setNum(num + 1)}>测试按钮:{num}</button>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
5.useMemo
只有当依赖的值发生变化才改变当前的值
const { useState, useMemo } = React
function App() {
const [num1, setNum1] = useState(2)
const [num2, setNum2] = useState(1)
// 只有当依赖的值发生变化,才改变当前的值
const s = useMemo(() => num1, [num2])
return (
<div className="main">
<button onClick={() => setNum1(num1 + 1)}>num1:{num1}</button>
<button onClick={() => setNum2(num2 + 1)}>num2:{num2}</button>
<h1>{s}</h1>
</div>
)
}
ReactDOM.render(<App />, document.getElementById('app'))
6.useContext
使用上下文,主要用于非相关组件的传参
const { useState, useContext, createContext } = React
const context = createContext()
const Product = () => {
const c = useContext(context)
return <li>{c.name}</li>
}
const Products = () => {
return (
<ul>
<Product />
</ul>
)
}
function App() {
return (
<div className="main">
<Products />
</div>
)
}
const AppProvider = ({ children }) => {
const [count, setCount] = useState(1)
return (
<context.Provider value={{ name: 'chen' }}>
{children}
</context.Provider>
)
}
ReactDOM.render(
// <context.Provider value={{ name: 'chen' }}>
// <App />
// </context.Provider>,
<AppProvider>
<App />
</AppProvider>,
document.getElementById('app')
)
7.useReducer
- useReducer 接收两个参数,参数一时一个function,参数二是初始值
- 我们如果要改变数据的时候使用dispatch派发一个action改变
export default function Counter () {
const [state,dispatch] = useReducer((state,action) => {
switch (action.type) {
case 'Add':
return { ...state, count: state.count + action.payload.step }
default:
return {...state}
}
}, { count: 1, list: [] })
//对reducer进行封装
export default function dataReducers(state, action) {
switch (action.type) {
// 实现加法
case 'Add':
return { ...state, count: state.count + action.payload.step }
// 实现减法
case 'Sub':
return { ...state, count: state.count - action.payload.step }
default:
return { ...state }
}
}
import dataReducers from '../reducers/reducer'
const [state, dispatch] = useReducer(dataReducers, { count: 1, list: [] })
//触发
<button
onClick={() => {
dispatch({
type: 'Add',
payload: {
step: 2,
},
})
}}
>
按钮{state.count}
</button>
四、Class定义组件
1.class定义组件
const { Component } = React
class App extends Component {
constructor(props) {
// 初始化的时候执行,可以获取属性和设置状态
super(props)
// 这句话的作用是为组件定义一个局部状态
this.state = {
count: 1,
}
}
addCount() {
this.setState({ count: this.state.count + 1 })
}
render() {
return (
<div className="main">
<button onClick={() => this.addCount()}>
点击按钮{this.state.count}
</button>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
2.this指向问题
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 1,
}
this.addCount2 = this.addCount2.bind(this)
}
// 使用箭头函数
// 使用初始化的时候改变this
// 使用bind
addCount1() {
this.setState({ count: this.state.count + 1 })
}
addCount2() {
// console.log(this)
this.setState({ count: this.state.count + 1 })
}
addCount3() {
this.setState({ count: this.state.count + 1 })
}
render() {
return (
<div className="main">
<button onClick={() => this.addCount1()}>
按钮A{this.state.count}
</button>
<button onClick={this.addCount2}>按钮B{this.state.count}</button>
<button onClick={this.addCount3.bind(this)}>
按钮C{this.state.count}
</button>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
3.父子组件传参
const { PureComponent } = React
class Count extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0,
}
}
render() {
return (
<button
onClick={() => {
this.setState({ count: this.state.count + 1 }, function () {
this.props.changeNum(this.state.count)
})
}}
>
子组件{this.state.count}
</button>
)
}
}
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
num: 0,
}
this.changeNum = this.changeNum.bind(this)
}
changeNum(v) {
this.setState({ num: v })
}
render() {
return (
<div className="main">
<Count changeNum={this.changeNum} />
<h1>{this.state.num}</h1>
</div>
)
}
}
ReactDOM.render(<App />, document.querySelector('#app'))
4.非相关组件传参
// 创建上下文
const context = createContext()
const AppProvider = ({ children }) => {
const [test, setTest] = useState(20)
return (
<context.Provider value={{ name: 'chen', test, setTest }}>
{children}
</context.Provider>
)
}
App.contextType = context
5.React生命周期
componentDidMount() {
console.log("组件挂载完成");
}
componentWillMount() {
console.log("组件将要挂载");
}
shouldComponentUpdate(nextProps, nextState) {
console.group("组件是否需要更新");
console.log(nextProps, nextState);
console.log(this.props, this.state);
// 判断是否需要更新,这个生命周期钩子函数主要用来做性能优化
// 如果改变的数据不需要在页面中进行展示,我们可以不更新组件 返回false
// console.log("组件是否需要更新");
console.groupEnd();
return this.state.count != nextState.count; //nextState.count % 2 == 0;
}
componentWillUpdate() {
console.log("组件将要更新");
}
componentDidUpdate() {
console.log("组件更新完成");
}
componentWillReceiveProps(nextProps) {
console.group("组件接收到新的属性");
console.log(nextProps);
console.groupEnd();
}
componentWillUnmount() {
console.log("组件将要销毁");
}
五、路由
所有的路由组件必须包含在<Router>包含的节点中使用
1.路由配置
- 在index.js中 import { HashRouter as Router } from “react-router-dom”;
<Router>
<App/>
</Router>
2.使用路由
- import {Route,Switch,Link} from ‘react-router-dom’
<Switch>
{/* exact 表示完全匹配 */}
{/* 这种写法是推荐的 */}
<Route path="/" exact>
<Home />
</Route>
{/* component属性表示访问这个path的时候展示的组件 */}
{/* 当我们使用component方式定义路由的组件的时候,会在组件中自动的把当前路由数据集成在组件的属性中 */}
<Route path="/list" component={List} />
{/* render是一个function,返回一个组件 */}
{/* params传参需要加占位符,占位符后面的?表示是一个可选参数 */}
<Route path="/detail/:p?" render={() => <Detail />} />
<ProtectedRoute path="/user">
<User />
</ProtectedRoute>
<Route path="/login">
<Login />
</Route>
</Switch>
3.配置私有路由
- 私有路由,做登录判断
- 使用结构赋值获取children和剩余的属性
import { Route, Redirect } from "react-router-dom";
import { isLogined } from "../utils/tools";
function ProtectedRoute({ children, ...rest }) {
return (
<Route
{...rest}
// render的作用就是返回一个当前对应路径展示的组件信息
// Redirect表示重定向
render={() => (isLogined() ? children : <Redirect to="/login" />)}
/>
);
}
4.路由传参
- // 路由传参的时候 可以直接把to定义为一个对象
- // pathname表示路径
- // search表示url中传递的参数
- // state也可以传参,但是页面刷新之后就没了
<Link
to={{
pathname: '/detail',
search: 'id=2&age=15',
state: { skills: '水枪', author: '小智' },
}}
>
5.路由中的hooks
- withRouter 是一个高阶函数
作用是对组件做一个封装,把路由数据填充在组件的props属性中,当我们使用component定义路由组件时,会自动将路由数据集成 在组件的属性中 - useLocation 获取当前路由中的location数据
- useParams 获取当前路由中的params数据
- useHistory 获取当前的history数据,可以实现编程式跳转
function Detail(props) {
const { search, state } = useLocation()
console.log(state)
const params = new URLSearchParams(search)
console.log(params.get('id'))
// console.log(useParams())
// console.log(useHistory())
return <div>这里是详情页</div>
}
export default withRouter(Detail)
六、redux
1.redux的基础使用
- 在redux中如果要改变数据只能dispatch派发一个action,在reducer中进行改变
- 在react中可以使用插件react-redux把redux中的数据传递到react组件中
// import { createStore } from 'redux'
import { Provider } from 'react-redux'
import store from './store'
// function AppReducer(state = { id: 1, count: 1 }, action) {
// switch (action.type) {
// case 'PLUS':
// return { ...state, count: state.count + 1 }
// default:
// return { ...state }
// }
// }
// const store = createStore(AppReducer)
// 在redux中如果要改变数据只能dispatch派发一个action,在reducer中进行改变
// store.disspatch({ type: 'PLUS' })
// 在react中可以使用插件react-redux把redux中的数据传递到react组件中
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
2.redux封装
import { createStore, combineReducers, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import appReducer from './reducer/app'
import todoReducer from './reducer/todo'
import productReducer from './reducer/product'
// combineReducers合成多个reducer为一个
const store = createStore(
combineReducers({
appReducer,
todoReducer,
productReducer,
}),
compose(
applyMiddleware(...[thunk]),
window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
)
)
export default store
reducer
const todoReducer = (
state = {
list: [
{ id: 1, name: '喜羊羊' },
{ id: 2, name: '懒羊羊' },
],
},
action
) => {
switch (action.type) {
case 'addTask':
return { ...state, list: [...state.list, action.payload] }
case 'delTask':
return {
...state,
list: state.list.filter((item) => item.id !== action.payload),
}
default:
return state
}
}
export default todoReducer
const productReducer = (state = { product: [] }, action) => {
switch (action.type) {
case 'getProduct':
return { ...state, product: [...state.product, ...action.payload] }
default:
return state
}
}
export default productReducer
action
import { loadProductApi } from '../../services/productApi'
export const getProductHandle = () => (dispatch) => {
loadProductApi().then((res) => {
dispatch({
type: 'getProduct',
payload: res.products,
})
})
}