浅学一下React

一些介绍

本文写了这么些东西:

1.介绍React Hooks,除了useImperativeHandle、useLayoutEffect和useDebugValue

2.介绍Redux

3.介绍Router

4.结合上面的东西实现一个小demo

尽量不讲概念,简单粗暴实现。已经如果你之前已经了解组件化思想,或者有用过Vue,读起来会省力一些。

参考资料:

1.官方文档

2.极简入门react:B站地址

3.每一个解答我疑问的旁友

Hooks

useState与useReducer

改变变量并触发视图更新,但useReducer更适用于逻辑复杂的数据更新

const [a,setA] = useState()
复制代码
 const [state, dispatch] = useReducer(reducer, initialState);
复制代码

累加器

使用useState实现

    const [num, setNum] = useState(0)
    function addNum() {
        setNum(num + 1)
    }
    return (
        <>
            <p>{num}</p>
            <button onClick={addNum}>+</button>
        </>
    )
复制代码

使用useReducer实现

    const initialNum = 0
    const [num, dispatch] = useReducer(addNum, initialNum)
    function addNum(state, action) {
        console.log(state, action)
        return state + 1
    }
    return (
        <>
            <p>{num}</p>
            <button onClick={dispatch}>+</button>
        </>
    )
复制代码

双向绑定

使用useState实现

    const [text, setText] = useState("higher~oh~higher")
    function changeText(value) {
        setText(value)
    }
    return (
        <>
            <p>{text}</p>
            <input type="text" onChange={(e) => changeText(e.target.value)} />
        </>
    )
复制代码

使用useReducer实现

    const initialText = "higher~oh~higher"
    const [text, setText] = useReducer(changeText, initialText)
    function changeText(_, value) {
        return value
    }
    return (
        <>
            <p>{text}</p>
            <input type="text" onChange={(e) => setText(e.target.value)} />
        </>
    )
复制代码

useEffect

在特定时间段进行操作,比如:请求初始数据、销毁数据、监听数据更新

模拟数据请求

类似于vue在组件加载时,请求初始数据。

    const [data, setData] = useState([1, 2, 3])
    const getData = () => {
        return new Promise((res) => {
            setTimeout(() => {
                res([4, 5, 6])
            }, 1000)
        })
    }
    useEffect(() => {
        getData().then((data) => {
            setData(data)
        })
    }, [])
    return (
        <>
            <ul>
                {data.map((i) => {
                    return <li key={i}>{i}</li>
                })}
            </ul>
        </>
    )
复制代码

监听数据更新

useEffect(()=>{
    ...
},[data])
复制代码

模拟销毁数据

useEffect(()=>{
    return ()=>{
        //在销毁前做点什么
    }
})
复制代码

useContext

可以从这个hook调用上下文空间,从而实现组件传值

createContext

使用这个api可以创建上下文空间。

createContext(默认值)
Provider:传递数据的一方
Consume:使用数据的一方
复制代码
const NumContext = createContext()
return <NumContext.Provider value={{num,setNum}}>
        <Child />
    </NumContext.Provider>
    
function Child(){
    return <NumberContext.Consumer>
            {({num}) => {
                    return <>{num}</>
                }}
        </NumberContext.Consumer>
}
复制代码

组件间传值

父子传值(或者说只有一层)

父->子

return <Child num={123} />
​
function Child(props){
    //可以获取到props.num
}
复制代码

子->父:父组件需要给子组件传递一个函数名,然后在自己组件内部实现这个函数;子组件需要传值的时候,触发这个函数即可。

const changeNum = ()=>{
    //更改num或者顺便做点别的,当然如果只需要更改Num,也可以将setNum作为参数传递
}
return <Child changeNum={changeNum} />
function Child(props){
    return <button onClick={()=>{props.changeNum()}}>change</button>
}
复制代码

上下文传值(多少层都可以)

const NumContext = createContext()
return <NumContext.Provider value={{num,setNum}}>
        <Child />
    </NumContext.Provider>
    
function Child(){
    const {num,setNum} = useContext(NumContext)
    return <>
        {num}
    </>
}
复制代码

useRef

获取某个组件或dom元素

一般在表单元素里,可以使用

<input value={text} onChange={(e)=>setText(e.target.value)} />
复制代码

来实现双向绑定。

但对于无法改变value的组件,想获取组件的一些属性,就可以使用这个hook

const el = useRef(null)
<input ref={el}/>
//可以通过el.current获取到这个dom元素
复制代码

useMemo与useCallback

避免子组件被强制更新消耗性能

memo

可以使用memo()把子组件包起来,只适用于纯静态组件

const Child = memo(()=>{
    return <>Child</>
})
复制代码

useCallback

对于一些要和父组件交互的子组件,可以在memo的基础上,把操作转移到父级

const Child = memo((props)=>{
    return <button onClick={()=>{props.changeNum()}}>click</button>
})
​
function App(){
    const [num,setNum] = useState(0)
    const changeNum = useCallback(()=>{setNum(num=>num+1)},[])
    return <>
        {num}
        <Child />
    </>
}
复制代码

useMemo

和上面差不多

const changeNum = useMemo(()=>{
    return ()=>{
        setNum(num=>num+1)
    }
},[])
复制代码

React Redux

基于Redux,结合Context设计出来的状态管理器

store

一般会新建一个同名文件夹,用index.js作为入口文件。

//src/store/index.js
import { createStore } from "redux"
​
const defaultState = {
    num: 1,
}
​
const reducer = () => {
    return defaultState
}
​
const store = createStore(reducer)
​
export default store
复制代码

Provider

一般给最顶层的组件套Provider

//src/index.js
import { Provider } from "react-redux"
import store from "./store"
​
ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>,
    document.getElementById("root")
)
复制代码

Connect

在组件中,需要获取数据的时候,需要在导出时使用connect,在组件内部实现state和dispatch,connect可以将state和dispatch都映射成props里的属性。

//src/App.jsx
import React from "react"
import { connect } from "react-redux"
​
function App(props) {
    return <div>num:{props.num}</div>
}
​
const storeState = (state) => {
    return {
        num: state.num,
    }
}
export default connect(storeState)(App)
复制代码

Consume

经过上面的步骤,就可以在组件内使用store的数据了

function App(props) {
    return <div>num:{props.num}</div>
}
复制代码

更改数据

  • 在reducer加上action参数,并根据action.type实现更改数据的操作。如果直接修改state,不会触发视图的更新。
const reducer = (state = defaultState, action) => {
    let newState = JSON.parse(JSON.stringify(state))
    if (action.type === "addNum") { //type多了的时候,可以改成switch
        newState.num++
    }
    return newState
}
复制代码
  • 在组件中,实现dispatch并放在connect的第二个参数里
const dispatchStore = (dispatch) => {
    return {
        addNum() {
            let action = { type: "addNum" }
            dispatch(action)
        },
    }
}
export default connect(storeState, dispatchStore)(App)
复制代码
  • 在组件html内部,可通过props调用dispatch
function App(props) {
    return (
        <>
            <div>num:{props.num}</div>
            <button
                onClick={() => {
                    props.addNum()
                }}
            >
                +1
            </button>
        </>
    )
}
复制代码

React - Router

router

新建一个文件夹存放路由组件,先来实现一个基本的路由。

import { BrowserRouter, Route, Routes } from "react-router-dom"
import App from "../App6"
import Home from "../pages/Home"
​
const BaseRouter = () => {
    return (
        <BrowserRouter>
            <Routes>
                <Route path="/" element={<App />}>
                </Route>
            </Routes>
        </BrowserRouter>
    )
}
​
export default BaseRouter
复制代码

想要Home组件在App组件内部渲染

<Route path="/" element={<App />}>
    <Route path="/home" element={<Home />}></Route>
</Route>
复制代码

最顶层的组件入口需要修改为路由的形式

import Router from "./router/index.jsx"
ReactDOM.render(
    <Router />,
    document.getElementById("root")
)
复制代码

父组件App要设置Outlet展示子路由

import { Outlet } from "react-router-dom"
export default function App6() {
    return (
        <>
            <Outlet />
        </>
    )
}
复制代码

跳转

完成上述设置后,启动项目,发现并没有展示home页面,需要手动在url后加上/home才能显示。可以使用几个hook来实现。

useLocation

获取当前路由的信息,其中包括path值

const xx = useLocation()
console.log(xx)
复制代码

image-20220315145344868.png

useNavigate

接收路由属性为参数,可进行路由跳转

const navigate = useNavigate()
console.log(navigate)
复制代码

image-20220315145653552.png

根据这两个Hook,可以使用useLocation获取当前路由的pathname,若为默认值,则使用useNavigate跳转至home页面。

useEffect(() => {
        //执行跳转逻辑
        if (pathname === "/") {
            navigate('/home')
        }
}, [])
复制代码

这样就可以实现vue-router里面默认重定向的效果了

Link

使用Link组件也可以实现路由跳转

<Link to="/home">Home</Link>
//相当于
<a href="/home">Back to Home</a>
复制代码

传参

如果是多级URL,比如/student/10001/report,则需要在路由里设置

<Route path="/student/:id" element={<Student/>}>
    <Route path="/report" element={<Report/>}></Route>
</Route>
复制代码

在目标页使用useParams获取参数

//Student.jsx
const {id} = useParams()
复制代码

如果是用查询字符串表示,比如/student?id='10001'/report,则可以使用useSearchParams

const [searchParams, setSearchParams] = useSearchParams()
searchParams.get('id')
复制代码

如果是使用navigate跳转传参,则可以用useLocation获取state

navigate("/student", { state: { id: '10001' } })
//Student.jsx
const { state } = useLocation()
console.log(state.id)
复制代码

404

<Route path="*" element={<Error />}></Route>
复制代码

小demo

好的,你已经充分了解react hooks和redux以及路由了。那么根据所学的知识来实现一个小demo吧。

那么这里有一个简易购物车,购物车里的数据从redux获得,可以点击某一项进入到商品详情,点击删除按钮删除这个数据。

数据结构设计

先来解决购物车数据的格式

car:[
    {//商品的简略信息
        good_id:1,
        good_name:'aaa',
        good_price:100,
        good_pic:url(),
        count:2
    }
]
goods:[
    {//商品的详细信息
        id:1,
        name:'aaa',
        price:100,
        pic:url(),
        desc:'这是一个什么什么'
    }
]
复制代码

列表组件

展示的是car的数据。

图片引入时,需要使用import而不是直接写在src

import goodImg from "...."
​
<img src={goodImg}/>
复制代码

在这个阶段碰到一个问题,如果子组件与父组件的样式重名了,父组件会覆盖子组件的样式。react中不像vue可以在标签里加个scoped就完事,但可以通过配置css module和css in js实现。如果样式关系没那么复杂,直接给两个组件设置不一样的样式名也行。

我在这个demo里使用了styled-components的方式,实现如下

//外层组件套了Provider store={store}
<ul>
    {props.car.map((i) => {
        return (
            <MyLi key={i.id} className="item">
                <div className="item-info">
                    <p className="title">商品名称:{i.name}</p>
                    <p className="price">商品单价:{i.price}</p>
                    <p className="nums">商品数量:{i.count}</p>
                    <p className="totle">
                        商品总价:¥{i.count * i.price}
                    </p>
                </div>
                <div className="item-tools">
                    <Button>查看</Button>
                    <Button>删除</Button>
                </div>
            </MyLi>
            )
    })}
</ul>
复制代码
//assets/css/list.js
import styled from "styled-components"
export const MyLi = styled.li`
    width: 300px;
    display: flex;
    justify-content: space-between;
    align-items: center;
    > div.item-info > .title {
        font-weight: bold;
    }
`
复制代码

详情组件

展示的是goods组件

需要通过good_id来展示详情,所以把路由改成

<Route path="/detail/:id" element={<Detail />}></Route>
复制代码

这样通过/detail/1就可以访问id为1的商品详情了。

详情组件实现如下:

function Detail(props) {
    const { id } = useParams()
    const [item] = props.goods.filter((i) => i.id === Number(id))
    return (
        <div>
            <p>商品名称:{item.name}</p>
            <p>商品单价:{item.price}</p>
            <p>商品详情:{item.desc}</p>
        </div>
    )
}
复制代码

如果没有找到这个商品,可以显示“该商品已下架”的提示。这里用到类似v-if+v-else的判断

item ? (
        <div>
            <p>商品名称:{item.name}</p>
            <p>商品单价:{item.price}</p>
            <p>商品详情:{item.desc}</p>
            <Link to="/home">Back to Home</Link>
        </div>
    ) : (
        <div>该商品已下架</div>
    )
}
复制代码

跳转逻辑

根据我的数据结构,当点击car里的某一项时,可以获取到good_id,将这个作为参数传递到详情组件里。然后可以根据这个id查找goods获取商品信息。

需要在点击查看按钮时,附带id数据跳转

const navigate = useNavigate()
    const toDetail = (id) => {
        navigate(`/detail/${id}`)
    }
复制代码

删除逻辑

购物车的数据是来源于store的,所以只要触发store的更改就可以了

在对store有多个操作的时候,可以用switch-case来匹配

const reducer = (state = defaultState, action) => {
    let newState = JSON.parse(JSON.stringify(state))
    const { type } = action
    switch (type) {
        case "addNum": {
            newState.num++
            break
        }
        case "deleteItem": {
            newState.car = newState.car.filter((i) => i.id !== action.id)
            break
        }
        default:
            break
    }
    return newState
}
复制代码

demo完成后就是这样

demo.gif

おすすめ

転載: juejin.im/post/7075909500540026894