一. 创建React项目
创建一个react-test项目:
npx create-react-app react-test
// 进入项目路径,启动项目
cd react-test
npm start
二. 类组件和函数组件
类组件
class App extends React.Component {
// construtor可省略
// construtor() {
// super()
// this.state = {
// count: 0
// }
// 在类组件初始化阶段,使用bind修正回调函数的this,指向当前组件实例对象
// this.clickHandler = this.clickHandler.bind(this)
// }
// 定义组件状态
state = {
count: 0
}
clickHandler () {
}
addCount = () => {
this.setState({
count: this.count + 1
})
}
render() {
return (
<div>
this is an component {this.state.count}
<button onClick={this.addCount}>修改count</button>
<button onClick={() => this.clickHandler}>使用箭头函数</button>
</div>
)
}
}
注:
- 类组件中不可以直接修改state的值,如
this.state.count += 1,这是错误的,应该使用this.setState({ count: this.state.count + 1 })
函数组件
使用js函数(或箭头函数)创建的组件
function App() {
const [count, setCount] = useState(0)
return (
<div>
this is an app {count}
<button onClick={() => setCount(count + 1)}>修改count</button>
</div>
)
}
三. 组件通信
组件是独立且封闭的单元,默认情况下只能使用自己的数据(state),组件通信是让不同组件之间能进行数据传递
父传子
1. 父组件提供要传递的数据
2. 给子组件标签添加数据
3. 子组件中通过props接收父组件中传过来的数据
-- 类组件中使用this.props获取props对象
-- 函数组件通过参数获取props对象
// 父组件 -- 函数组件
function App() {
const [count, setCount] = useState(0)
return (
<>
<SonFunc count={count} />
<SonClass count={count} />
<button onClick={() => setCount(count + 10)}>count+10</button>
</>
)
}
// 父组件 -- 类组件
class App extends React.Component {
state = {
count: 0
}
addCount = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<>
<Son count={this.state.count} />
<button onClick={this.addCount}>count+10</button>
</>
)
}
}
// 子组件一:函数组件
// 也可使用解构赋值获取数据:function Home({ count })
function SonFunc(props) {
return (
<div>这是子组件一(函数组件)中的接收的值:{props.count}</div>
)
}
// 子组件二:类组件
class SonClass extends React.Component {
render() {
return (
<div>这是子组件二(类组件)中的接收的值:{this.props.count}</div>
)
}
}
注:
- props是只读对象:根据单向数据流的要求,子组件只能读取props中的数据,不能修改,
this.props.count += 1错误 - props可以传递任意数据:数字、字符串、布尔值、数组、对象、函数、jsx
子传父
子组件调用父组件传递过来的函数,将传给父组件的数据当作函数实参
-- 可以实现子组件修改父组件的state
// 父组件
function App() {
const [count, setCount] = useState(0)
const modifyCount = (params) => {
setCount(params)
}
const getSonMsg = (msg) => {
console.log(msg)
}
return (
<Son getSonMsg={getSonMsg} modifyCount={modifyCount} />
)
}
// 子组件
function Son(props) {
return (
<div>这是子组件 <button onClick={() => props.getSonMsg('来自子组件的数据')}>click</button></div>
)
}
兄弟组件通信
1. 先把A中的数据通过子传父传给父组件
2. 再把父组件中接收到的数据通过父传子传给B
// 父组件
function App() {
const [sendMsg, setSendMsg] = useState('传给B的数据')
const getMsg = (msg) => {
setSendMsg(msg)
}
return (
<>
<SonA getMsg={getMsg} />
<SonB sendMsg={sendMsg} />
</>
)
}
// 子组件A
function SonA({ getMsg }) {
const msg = '来自组件A的数据'
return (
<div>这是子组件A <button onClick={() => getMsg(msg)}>发送数据</button></div>
)
}
// 子组件B
function SonB({sendMsg}) {
return (
<div>这是组件B,{sendMsg}</div>
)
}
跨组件通信Context
上层组件向任意一个下层组件传递数据,无需使用props一层层传递
// 1. 创建Context对象,到处Provider和Consumer对象
const { Provider, Consumer } = createContext()
// 2. 使用Provider包裹根组件提供数据
<Provider value={this.state.msg}>
{/* 根组件 */}
</Provider>
// 3. 需要使用数据的组件用Consumer包裹获取数据
<Comsumer>
{value => /* 基于context值进行渲染 */}
</Consumer>
代码实现:
import { createContext } from 'react'
// 跨组件通信App -> A -> B
const { Provider, Consumer } = createContext()
function ComA() {
return (
<div>
这是组件A
<ComB />
</div>
)
}
function ComB() {
return (
<div>
这是组件B
<Consumer>
{value => <span>{value}</span>}
</Consumer>
</div>
)
}
function App() {
const msg = '来自根组件的数据'
return (
<Provider value={msg}>
<ComA />
</Provider>
)
}
四. Hooks
Hooks的出现解决了两个问题:
1. 组件的状态逻辑复用 2.class组件自身的问题(各种生命周期钩子函数、this指向问题)
只能在函数内部的最外层*调用 Hook,不要在循环、条件判断或者子函数中调用
Hooks优势总结:
- 告别难以理解的class
- 解决业务逻辑难以拆分的问题
- 使状态逻辑复用变得简单易行
- 函数组件在设计思想上,更加契合React的理念
useState
const [count, setCount] = useState(0)
// 使用回调函数的返回值作为初始值
const [count, setCount] = useState(() => {
return props.count * 2
})
// 更改count值
<button onCLick={() => setCount(count + 1)}> count值+1 </button>
注:
- useSate的初始参数只会在组件首次渲染时使用,再次更新时会被忽略
- 每次通过setCount修改状态都会引起组件重新渲染
- useState可以使用多次,每次都是独立的
- useState不可以在除了函数组件之外的地方,如分支语句、循环语句、内部函数中执行
useEffect
为React组件提供副作用处理:
副作用指那些没有发生在数据向视图转换过程中的逻辑,函数的主作用是通过数据渲染UI,除了主作用之外的操作就是副作用
1. ajax请求
2. 访问原生dom元素
3. 本地持久化缓存:localStorage操作
4. 绑定/解绑事件
5. 添加订阅
6. 设置定时器
7. 记录日志
- 不传递第二个参数
// 组件首次渲染和每次更新时都会调用
useEffect(() => {
console.log('副作用执行')
})
- 第二个参数传递空数组
// 组件首次渲染和卸载时执行
useEffect(() => {
console.log(props)
}, [])
- 添加特定依赖项
// 首次渲染和依赖项(任意一个值count/name)变化时执行
useEffect(() => {
console.log(count)
console.log(name)
}, [count, name])
注:
- 副作用return一个方法,会在组件销毁时调用,可用于清除计时器
- useEffect不能接收async作为回调函数,需要在内部定义async函数,再在内部调用(useEffect 接收的函数,要么返回一个能清除副作用的函数,要么就不返回任何内容,async 返回的是 promise)
- useEffect回调在dom渲染完成之后执行
// 1. 副作用return一个方法,会在组件销毁时调用,用于清除副作用
useEffect(() => {
return () => {
clearInterval(timer)
}
}, [])
// 2. useEffect不可以直接写async,需要在内部定义async函数,再调用
// useEffect(async () => {}, []) 这样是错误的
useEffect(() => {
async function getData() {
let res = await fetch('xxx')
}
getData()
}, [])
useRef
获取真实dom或组件实例对象
useContext
提供在hooks下跨组件通信
注:
- 如果提供数据是静态不变的,在index.js里包裹
- 如果提供数据需要变化,在app.js里方便更改数据
useCallback
React hooks可参考 React Hooks 有详细介绍
五. react-router
安装:npm install react-router-dom@6
基本使用
import { BrowserRouter, Routes, Route, Link} from 'react-router-dom'
function App() {
return (
<BrowserRouter>
{/* 点击跳转 */}
<Link to="/home">首页</Link>
<Link to="/about">关于</Link>
{/* 路由出口位置 */}
<Routes>
<Route path="home" element={<Home />}></Route>
<Route path="about" element={<About />}></Route>
</Routes>
</BrowserRouter>
)
}
核心组件
- BrowserRouter
- 包裹整个应用,一个React应用只需使用一次
- 两种常用Router:BrowserRouter:使用H5的history.pushState实现(/about),HashRouter:使用URL的哈希值实现(/#/about)
- Link
- 用于指定导航链接,完成路由跳转
- 组件通过to属性指定路由地址,最终被渲染为a标签
- Routes
- 提供一个路由出口,满足条件的路由组件会渲染到组件内部
- Route
- 用于指定导航链接,完成路由匹配
- path属性指定匹配的路径地址,element属性指定要渲染的组件
编程式导航(跳转与参数)
import { useNavigate } from 'react-router-dom'
// 组件内部执行useNavigate得到一个跳转函数
const navigate = useNavigate()
// 调用跳转函数传入目标路径
navigate('/about', {replace: true}) // replace默认false
跳转传递参数:
- searchParams传参
// 传参
navigate('/about?id=100')
// 取参
import { useSearchParams } from 'react-router-dom'
let [params] = useSearchParams()
let id = params.get('id') // 传递两次的话取第一个
- params传参
// 须在路由里配置参数
<Route path="about/:id" element={<About />}></Route>
// 传参
navigate('/about/100')
// 取参
import { useParams } from 'react-router-dom'
let params = useParams()
let id = params.id
嵌套路由
<Routes>
<Route path="home" element={<Home />}></Route> // 一级路由
<Route path="about" element={<About />}>
<Route path="board" element={<Board />}></Route> // 二级路由
<Route index" element={<News />}></Route> // 默认显示二级路由
</Route>
</Routes>
// 二级路由出口,使用Outlet
import { Outlet } from 'react-router-dom'
function About() {
return (
<div>
About
<Outlet />
</div>
)
404页配置
// 当所有路径都没有匹配时,在各级路由的 最后 添加*号路由兜底
<Route path="*" element={<NotFound />}></Route>
路由可参考 https://reactrouter.com/docs/zh/v6 实现更详细的配置