一、React基础
起步
1、安装官方脚手架: npm install -g create-react-app
2、创建项目:create-react-app react-project-name
3、启动项目:npm start
4、暴露配置项:npm run eject
入口文件:index.js
ReactDOM.render(<App />, document.getElementById('root'));
babel-loader 最终会把jsx转换成js能识别的内容
JSX:是一种JavaScript的语法扩展,其格式比较像模板语言,但事实上完全是在JavaScript内部实现的
JSX实质就是 React.createElement 的调用,最终的结果是React元素(JavaScript对象)
const jsx = <div>hello react</div>ReactDOM.render(jsx, document.getElementById('root'));
组件
组件是抽象的独立功能模块,react应用程序由组件构建而成
组件的两种表现形式:class组件 function组件
class组件通常拥有状态(state)和生命周期(lifeCycle),继承于Component,实现render方法
import React, { Component } from 'react'export default class Home extends Component {
render() {
return (
<div>
我是home页面
</div>
)
}
}
function组件
函数组件通常无状态,仅关注内容展示,返回渲染结果即可。
---从React16.8开始引入了hooks,函数组件也能够拥有状态。
写一个定时器显示当前组件
import React, { Component } from 'react'export default class Home extends Component {
constructor(props){
super(props)
this.state = {
date: new Date()
}
}
// 组件挂载后---生命周期函数
componentDidMount(){
this.timerId = setInterval(()=>{
this.setState({
date: new Date()
})
}, 1000)
}
// 组件卸载之前
componentWillUnmount(){
clearInterval(this.timerId)
}
render() {
const str = '我是home页面'
const {date} = this.state
return (
<div>
<h1>{str}</h1>
<p>{date.toLocaleTimeString()}</p>
</div>
)
}
}
setState方法
setCounter(){ // 这样写找不到thisthis.setState({
counter: this.state.counter + 1
})
}
// 解决方法一
constructor(props){
super(props)
this.state = {
date: new Date(),
counter: 0
}
this.setCounter = this.setCounter.bind(this)
}
setCounter(){
this.setState({
counter: this.state.counter + 1
})
}
// 解决方法二
setCounter = () => {
this.setState({
counter: this.state.counter + 1
})
}
setState为异步,如何将其实现同步
setCounter = () => {this.setState({
counter: this.state.counter + 1
})
console.log(this.state.counter) // 这里打印0,页面显示1
}
// 打印0时,页面显示2,并且每次只加2
setCounter = () => {
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 2
})
console.log(this.state.counter)
}
// 实现同步
// 方式一
setCounter = () => {
this.setState(nextState => {
return {
counter: nextState.counter + 1
}
})
this.setState(nextState => {
return {
counter: nextState.counter + 2
}
})
}
// 方式二
setCounter = () => {
setTimeout(() => {
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 2
})
console.log(this.state.counter)
}, 0)
}
// 方式三:原生事件上去绑定
componentDidMount(){
document.getElementsByTagName('button')[0].addEventListener('click', ()=>{
this.setState({
counter: this.state.counter + 1
})
this.setState({
counter: this.state.counter + 2
})
})
}
总结:setState只有在合成事件和钩子函数中是异步的,在原生事件和setTimeout、setInterval中都是同步的
function组件
function组件是没有状态和生命周期的
如果一定要使用state,可以进行引入 useState、useEffect
下列代码将上面的class组件转成funciton组件
import React, {useState, useEffect} from 'react'export default function User() {
const [date, setDate] = useState(new Date())
useEffect(() => {
const timerId = setInterval(() => {
setDate(new Date())
}, 1000)
return () => clearInterval(timerId)
})
return (
<div>
<h1>我是user页面</h1>
<p>{date.toLocaleTimeString()}</p>
</div>
)
}
事件处理
onclick事件和onchange事件
import React, { Component } from 'react'export default class Search extends Component {
constructor(props){
super(props)
this.state = {
name: '12'
}
}
handler = () => {
console.log('handler');
}
change = (event) => {
let value = event.target.value
this.setState({
name: value
})
console.log('change', this.state.name);
}
render() {
const {name} = this.state
console.log(this);return (
<div>
<h1>我是Search页面</h1>
<button onClick={this.handler}>click</button>
<input onChange={this.change} value={name}/>
</div>
)
}
}
事件回调函数注意绑定this指向,常见三种方法:
1、构造函数中绑定并覆盖:this.change = this.change.bind(this)
2、方法定义为箭头函数:change = ()=>{}
3、事件中定义为箭头函数:onChange = { () => this.change() }
--- react里遵循单项数据流,没有双向绑定,输入框要设置value和onChange,成为受控组件。
组件通信
props属性传递
// App.js中const store = {
userInfo: {
userName: 'Dylan'
}
}
function tellme(msg){
console.log('tellme:', msg);
}
function App() {
return (
<div className="App">
<Search store={store} tellme={tellme} />
</div>
);
}
export default App;
// Search.js中
handler = () => {
console.log('handler');
const {tellme} = this.props
tellme('im dylanLv')
}
render() {
const {name} = this.state
console.log(this);
const {userInfo} = this.props.store
return (
<div>
<h1>我是Search页面,{userInfo.userName}</h1>
<button onClick={this.handler}>click</button>
<input onChange={this.change} value={name}/>
</div>
)
}
如果父组件传递的是函数,则可以把子组件信息传入父组件,这个常称为状态提示
react16的生命周期
import React, {Component} from 'react'export default class LifeCycle extends Component {
constructor(props) {
super(props)
this.state = {
counter: 0
}
console.log('constructor', this.state.counter);
}
// 挂载之前
UNSAFE_componentWillMount() {
console.log('componentWillMount', this.state.counter);
}
// 挂载之后
componentDidMount() {
console.log('componentDidMount', this.state.counter);
}
// 更新之前
UNSAFE_componentWillUpdate() {
console.log('componentWillUpdate', this.state.counter);
}
// 更新之后
componentDidUpdate() {
console.log('componentDidUpdate', this.state.counter);
}
// 卸载之前
componentWillUnmount() {
console.log('componentWillUnmount', this.state.counter);
}
// 是否更新--执行render--优化使用
shouldComponentUpdate(nextProps, nextState) {
console.log('shouldComponentUpdate', this.state.counter, nextState.counter, nextProps);
return this.state.counter != 5
}
setCounter = () => {
this.setState({
counter: this.state.counter + 1
})
}
render() {
console.log('render');
const {counter} = this.state
return (
<div>
<h1>我是一个lifeCycle, Hylan</h1>
<p>{counter}</p>
<button onClick={this.setCounter}>click</button>
{!!(counter % 2) && <Foo/>}
</div>
)
}
}
class Foo extends Component {
componentWillUnmount() {
console.log('componentWillUnmount foo');
}
render() {
return <div>我是FOO组件</div>
}
}
V17可能会废弃的三个生命周期函数用 getDerivedStateFromProps 替代,目前使用的话加上 UNSAFE_
- componentWillMount
- componentWillReceiveProps
- conponentWillUpdate
引入两个新的生命周期
- static getDerivedStateFromProps
- getSnapshotBeforeUpdate
static getDerivedStateFromProps(props, state){// getDerivedStateFromProps 会在调用render方法之前调用
// 并且在初始挂载以及后续更新时都会被调用
// 它应返回一个对象来更新 state ,如果返回 null 则不更新任何内容
const { counter } = state
console.log('getDerivedStateFromProps', counter)
return counter < 8 ? null : { counter: 0 }
}
// 上一次(更新前)的影像
// 返回值会作为 componentDidUpdate 的第三个参数
getSnapshotBeforeUpdate(prevProps, prevState){
const { counter } = prevState
console.log('getSnapshotBeforeUpdate', counter)
return null
}
// 更新之后
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('componentDidUpdate', this.state.counter);
}
使用Context 上下文传值---可以跨层级传值
// App.jsimport React from 'react';
import './App.css';
import Home from './pages/Home';
const Context = React.createContext()
const Provider = Context.Provider
const Consumer = Context.Consumer
const store = {
home: {},
user: {
name: 'Dylan'
}
}
function App() {
return (
<div className="App">
<Provider value={store}>
<Consumer>
{ctx => <Home {...ctx}/>}
</Consumer>
</Provider>
</div>
);
}
export default App;
// Home.js
import React, { Component } from 'react'
export default class Home extends Component {
render() {
// home:{
// home: {}
// user: {name: "Dylan"}
// }
console.log("home", this.props);
return (
<div>
home
</div>
)
}
}
一般使用时会将Context暴露在外部,使用者将其引用就行了
AppContext.js 文件
import React from 'react';const Context = React.createContext()
export const Provider = Context.Provider
export const Consumer = Context.Consumer
App.js 文件
import React from 'react';import './App.css';
import Home from './pages/Home';
import {Provider, Consumer} from './AppContext'
const store = {
home: {},
user: {
name: 'Dylan'
}
}
function App() {
return (
<div className="App">
<Provider value={store}>
<Consumer>
{ctx => <Home {...ctx}/>}
</Consumer>
</Provider>
</div>
);
}
export default App;
跨层级传值
App.js 文件
const store = {home: {},
user: {
name: 'Dylan'
}
}
function App() {
return (
<div className="App">
<Provider value={store}>
<Home />
</Provider>
</div>
);
}
Home.js文件
import React, { Component } from 'react'import {Consumer} from '../AppContext'
export default class Home extends Component {
render() {
console.log("home", this.props);
return <Consumer>
{ctx => <HomeHandle {...ctx}/>}
</Consumer>
}
}
function HomeHandle(props){
console.log('HomeHandle', props);
return <div>
<h1>HomeHandle</h1>
</div>
}
组件复合---Composition
TabBar.js 文件
import React, {Component} from 'react'export default class TabBar extends Component {
render() {
return <div className="tabBar">TabBar</div>
}
}
Layout.js 文件
import React, {Component} from 'react'import TabBar from '../components/TabBar'
export default class Layout extends Component {
render() {
const {children, showTabBar = true} = this.props
return (
<div>
{children}
{showTabBar && <TabBar />}
</div>
)
}
}
Home.js 文件 ,使用Layout模板
import React, {Component} from 'react'import {Consumer} from '../AppContext'
import Layout from './Layout';
export default class Home extends Component {
render() {
return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
}
}
function HomeHandle(props) {
console.log('HomeHandle', props);
return <Layout showTabBar={true}>
<div>
<h1>Home</h1>
</div>
</Layout>
}
上述类似 vue 中的匿名插槽
下面代码展示具名插槽
Home.js 文件
import React, {Component} from 'react'import {Consumer} from '../AppContext'
import Layout from './Layout';
export default class Home extends Component {
render() {
return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
}
}
function HomeHandle(props) {
console.log('HomeHandle', props);
return <Layout>
{{
btn: <button>按钮</button>,
content: "我是内容"
}}
</Layout>
}
Layout.js 文件
import React, {Component} from 'react'import TabBar from '../components/TabBar'
export default class Layout extends Component {
componentDidMount(){
const { title='商场' } = this.props
document.title = title
}
render() {
const {children, showTabBar} = this.props
console.log('props:',this.props);
return (
<div>
{children.btn}
{children.content}
{showTabBar && <TabBar />}
</div>
)
}
}
如何让layout自行判断传进来的组件是具名还是匿名
Layout.js 文件
render() {const {children, showTabBar = true} = this.props
const a = []
// 如果有这个值($$typeop) , 说明是匿名的
if(children.$$typeof){
a.push(children)
}else{
for(let item in children){
a.push(children[item])
}
}
return (
<div>
{
a.map((item, index) => {
return <div key={`child${index}`}>{item}</div>
})
}
{showTabBar && <TabBar />}
</div>
)
高阶组件
将Home.js 进行修改
import React, {Component} from 'react'import {Consumer} from '../AppContext'
import Layout from './Layout';
const consumerHandler = Cmp => props => {
return <Consumer>
{ctx => <Cmp {...props} {...ctx}/>}
</Consumer>
}
function HomeHandle(props) {
console.log('HomeHandle', props);
return <Layout>
<div>
<h1>home</h1>
</div>
</Layout>
}
export default consumerHandler(HomeHandle)
可以将其放入 AppContext.js 文件(公共文件)
import React from 'react';const Context = React.createContext()
export const Provider = Context.Provider
export const Consumer = Context.Consumer
export const consumerHandler = Cmp => props => {
return <Consumer>
{ctx => <Cmp {...props} {...ctx}/>}
</Consumer>
}
高阶组件---HOC
为了提高组件复用性,可测试性,就要保证组件功能单一性;但是若要满足复杂需求就要扩展功能单一的组件,在React里就有了HOC(Higher-Order Components)的概念。
定义:高阶组件就是一个工厂函数,它接收一个组件并返回另一个组件。
改写 App.js,展示高阶组件的用法
function Child(props){return <div>child</div>
}
const foo = Cmp => props => {
return <div style={{border: '1px solid black'}}>
<Cmp {...props}/>
</div>
}
function App() {
const Foo = foo(Child)
return (
<div className="App">
<Child />
<Foo />
{/* <Home /> */}
{/* <User /> */}
</div>
);
}
export default App;
高阶组件实际上就是,你给我一个组件,我把这个组件封装一下,再返回给你一个封装后的组件
高阶组件可以链式调用
function Child(props){return <div>child</div>
}
const foo = Cmp => props => {
return <div style={{border: '1px solid black'}}>
<Cmp {...props}/>
</div>
}
const foo2 = Cmp => props => {
return <div style={{border: '1px solid red', padding: '10px'}}>
<Cmp {...props}/>
</div>
}
function App() {
const Foo = foo2(foo(Child))
return (
<div className="App">
<Child />
<Foo />
</div>
);
}
export default App;
装饰器写法
高阶组件本身是对装饰器模式的应用,自然可以利用ES7中出现的装饰器语法来更优雅的书写代码。
CRA项目中默认不支持js代码使用装饰器语法,可修改后缀名为 tsx 则可以直接支持
Hooks
Hook是React16.8的一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hooks的特点:
- 是你在无需修改组件结构的情况下复用状态逻辑
- 可将组件中相互关联的部分拆分成更小的函数,复杂组件将变得更容易理解
- 更简洁、更易理解的代码
hook中使用子组件,代码如下:
import React, {useState, useEffect} from 'react'export default function HooksPage() {
const [date, setDate] = useState(new Date())
const [fruits, setFruits] = useState(['apple', 'banana'])
useEffect(() => {
const timerId = setInterval(() => {
setDate(new Date())
}, 1000)
return () => clearInterval(timerId)
})
return (
<div>
hooks
<p>{date.toLocaleTimeString()}</p>
{/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
<FruitList fruits={fruits} setFruits={setFruits}/>
</div>
)
}
function FruitList({fruits, setFruits}) {
// 点击后删除当前项
const delCur = index => {
const tem = [...fruits]
tem.splice(index, 1)
setFruits(tem)
}
return <div>
<ul>
{
fruits.map((item, index) => {
return <li key={`fruit${index}`} onClick={()=>delCur(index)}>{item}</li>
})
}
</ul>
</div>
}
添加一个新增 fruits 操作
export default function HooksPage() {const [date, setDate] = useState(new Date())
const [fruits, setFruits] = useState(['apple', 'banana'])
useEffect(() => {
const timerId = setInterval(() => {
setDate(new Date())
}, 1000)
return () => clearInterval(timerId)
})
return (
<div>
hooks
<p>{date.toLocaleTimeString()}</p>
{/* 传入addFruit函数 */}
<AddFruit addFruit={item=>setFruits([...fruits, item])} />
{/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
<FruitList fruits={fruits} setFruits={setFruits}/>
</div>
)
}
function AddFruit({addFruit}) {
const [name, setName] = useState("")
return <div>
<input value={name} onChange={e => setName(e.target.value)} />
{/* 接收addFruit函数,点击按钮进行触发 */}
<button onClick={()=>addFruit(name)}>点击增加</button>
</div>
}
如果在 useEffect 中加入 console.log('useEffect'),可以发现不管是 setDate执行,还是点击新增按钮后的 setFruits 执行,console.log都会执行。
但实际上当前功能只需要 date的定时器不断执行而已,可以修改代码进行优化
useEffect(() => {console.log('useEffect');
const timerId = setInterval(() => {
setDate(new Date())
}, 1000)
return () => clearInterval(timerId)
// useEffect的第二个参数,加入后就只对 date 生效了
}, [date])
接下来 点击 新增按钮 ,就不会打印 useEffect 了
副作用钩子
useEffect给函数组件增加了执行副作用操作的能力。
副作用(Side Effect)是指一个function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log ,所以ajax操作,修改dom都算做副作用。
- 异步数据获取,更新HooksTest.js
useEffect(() => {setTimeout(()=>{
setFruits(['香蕉','西瓜'])
}, 1000)
})
测试就会发现副作用操作会被频繁调用
- 设置依赖
// 设置空数组意为没有依赖,则副作用操作仅执行一次useEffect(()=>{...}, [])
- 清除工作:有一些副作用是需要清除的,清除工作非常重要,可以防止引起内存泄漏
useEffect(() => {const timerId = setInterval(() => {
console.log('mgs');
}, 1000)
return () => clearInterval(timerId)
}, [])
组件卸载后回执行返回的清理函数
useReducer
useReducer是useState的可选项,常用于组件有复杂状态逻辑时
将上面的代码改成使用 useReducer
Fruit.js 文件
import React, {useState, useEffect} from 'react'export function AddFruit({addFruit}) {
const [name,
setName] = useState("")
return <div>
<input value={name} onChange={e => setName(e.target.value)}/>
<button onClick={() => addFruit(name)}>点击增加</button>
</div>
}
// FruitList 组件需要传入 fruits 和 setFruits
export function FruitList({fruits, setFruits}) {
// 点击后删除当前项
const delCur = index => {
const tem = [...fruits]
tem.splice(index, 1)
setFruits(tem)
}
return <div>
<ul>
{
fruits.map((item, index) => {
return <li key={`fruit${index}`} onClick={() => delCur(index)}>{item}</li>
})
}
</ul>
</div>
}
HookReducer.js 文件
import React, { useEffect, useReducer } from 'react'import { FruitList, AddFruit } from '../components/Fruit'
function fruitsReducer(state, action){
switch(action.type){
case 'init':
case 'replace':
return action.paylod
default:
return state
}
}
export default function HooksReducer() {
const [fruits, dispatch] = useReducer(fruitsReducer, [])
useEffect(()=>{
setTimeout(()=>{
dispatch({type: 'init', paylod: ['apple', 'banana']})
}, 1000)
return ()=>{
// clearup
}
}, [])
return (
<div>
HooksReducer
<FruitList fruits={fruits} setFruits={(newFruitList)=>dispatch({type: 'replace', paylod: newFruitList})}/>
</div>
)
}
useContext
useContext 用于 快速在函数组件中导入上下文。---之前是用Consumer做的
import React, { useContext } from 'react'const Context = React.createContext()
const Provider = Context.Provider
export default function HooksContext() {
const store = {
user: {
name: 'Dylan'
}
}
return (
<div>
<Provider value={store}>
<ContextChild></ContextChild>
</Provider>
</div>
)
}
function ContextChild(props){
console.log(useContext(Context));
const {user} = useContext(Context)
return <div>
{user.name}
</div>
}
以上是 一、React基础 的全部内容, 来源链接: utcz.com/z/381281.html