这是一篇十分十分十分基础的笔记,不要报过高的期望......如有错误的地方,还请大佬指正
1 React基础
1.1 JSX语法
    JSX语法是一种类似于html标签的语法,它的作用相当于是让我们在JavaScript代码中直接写html代码,但是JSX不完全是html,它是JavaScrip 的一种扩展语法,它具有JavaScript的全部能力,我们还可在JSX代码中插入变量或者表达式,用JSX语法写出来的语句是一个对象,我们可以将它存为一个变量,这个变量作为ReactDOM对象的render方法的第一个参数。
<!-- 加载React -->
<script src="https://cdn.staticfile.org/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdn.staticfile.org/react-dom/16.4.0/umd/react-dom.development.js"></script>
<!-- 转换JSX 生产环境中不建议使用 浏览器中使用Babel来编译JSX效率非常低 -->
<script src="https://cdn.staticfile.org/babel-standalone/6.26.0/babel.min.js"></script>
    jsx可以是嵌套结构,如果是嵌套结构,需要有唯一的一个外层标签<></>。如果是单标签,在结尾要加"/",在jsx中可以通过"{}"插入变量,表达式或者函数调用。
<div id="root"></div>
<script type="text/babel">
// script type="text/babel"
const num = 0
const str = 'Tifa'
const isOk = true
const revStr = str => str.split('').reverse().join('')
const el = (
<div>
{/* jsx注释定义方法 */}
<h3>jsx语法</h3>
{/* 插入变量及运算 */}
<p>{ num + 1 }</p>
{/* 插入表达式 */}
<p>{ str.split('').reverse().join('') }</p>
{/* 插入函数调用 */}
<p>{ revStr(str) }</p>
{/* 插入三元运算表达式 */}
<p>{ isOk?'YES':'NO' }</p>
{/* 内嵌样式&行间样式 */}
<p className="bg-red">内嵌样式</p>
<p style={{width:'200px',height:'200px',backgroundColor:'cyan'}}>行内样式</p>
{/* 单标签,结尾要加"/" */}
<img src="../images/tifa.png" />
</div>
)
ReactDOM.render(el,document.getElementById('root'))
</script>
1.2 函数组件
    组件可以理解成是一个组成页面的部件或者零件,每个部件都有自己完整的结构和功能,多个部件拼装在一起就可以组成一个页面,组件最终是要返回一个jsx对象,组件有两种定义方式:一种是函数式定义,一种是类定义。
// 组件名称首字母要大写
function Welcome () {
return <h1>Hello,Tifa</h1>
}
// 函数可接收props参数,props是一个对象,对象中的属性是在使用组件时传入的
function WelcomeName (props) {
return <h1>Hello, {props.name}</h1>
}
// <WelcomeName name='Tifa' />
    组件的渲染和 jsx 对象一样,我们可以通过 ReactDOM.render() 方法来渲染组件,组件以标签的方式使用,可以写成单个标签或者双标签,写成单个便签,结尾要加"/"
// ReactDOM.render(<Welcome />, document.getElementById('root')
ReactDOM.render(<WelcomeName name="Tifa" />, document.getElementById('root')
// 参数用解构
const WelcomeName = ({name}) => <h1>Hello, {name}</h1>
// 拼装组件
const App = () => (
<div>
<WelcomeName name="Tifa" />
<WelcomeName name="Aerith" />
{/* <WelcomeName name="Cloud" /> */}
</div>
)
ReactDOM.render(<App />, document.getElementById('root'))
1.3 类组件
    类组件就是通过ES6类Class的方式来定义组件。定义的类需要继承于React.Component类,类组件最少需要有一个render方法,这个方法会返回一个jsx对象,类里面使用的props属性是在React.Component类上面继承过来的,所以可以直接使用。
class WelcomeName extends React.Component {
render () {
return <h1>Hello, {this.props.name}</h1>;
}
}
ReactDOM.render(<WelcomeName name="Tifa" />,document.getElementById('root')
    组件以标签的形式使用时,可以用单标签,也可以用双标签,如果以双标签的方式使用,在双标签中插入内容,那么组件的props上就有了children属性,children属性值就是组件标签中间的内容,可以是文本、jsx对象、函数或者组件。
class App extends Component {
render () {
return <Cloud>{ () => alert(Date.now()) }</Cloud>
}
}
class Cloud extends Component {
render () {
return (
<>
<h2>children属性:</h2>
<button onClick={ this.props.children }>点我显示时间戳</button>
</>
)
}
}
1.4 事件绑定
    React事件绑定与js的行间事件类似,事件绑定是写在标签中的,事件用小驼峰命名,事件需要传递一个函数作为事件处理程序,可以通过类定义组件,将这个函数作为一个方法定义在组件中。
class Hello extends React.Component {
// 建议使用箭头函数 不用操心this的问题
info = () => {
alert(`名字:${this.props.name}--年龄:${this.props.age}`)
}
skill = name => {
alert(`技能名:${name}`)
}
render () {
return (
<div>
<input type='button' value='点我呀' onClick={this.info} />
{/* 给绑定函数传递参数时 用箭头函数包裹一下 */}
<input type='button' value='放大招' onClick={()=>this.skill('小拳拳捶你~~~')} />
</div>
)
}
}
ReactDOM.render(
<Hello name='Tifa' age={18} />,
document.getElementById('root')
)
1.5 State
    拥有state属性的组件叫做有状态组件,没有state属性的组件叫做无状态组件。所以用函数方式定义的组件都是无状态组件,用类的方式定义的组件,定义了state就是有状态组件,没定义state就是无状态组件。
<div id="root"></div>
<script type="text/babel">
class Increase extends React.Component {
constructor (props) {
super(props)
this.state = {
num : 0
}
}
addNum = () => {
this.setState(state => ({ num: state.num + 1 }))
}
// 设置state里面的值用setState,不能直接修改state中的值
// setState里面可以直接传递一个对象,对象可以是整体或者是部分的state(键值对)
// setState里面还可以传递一个函数返回一个对象
// 函数的参数中传递的第一个参数是state上一个状态的值
render () {
return (
<div>
<p> { this.state.num } </p>
<button onClick={ this.addNum }>点我递增</button>
</div>
)
}
}
ReactDOM.render(<Increase />, document.getElementById('root'))
</script>
    注意: React 可能会把多个 setState() 调用合并成一个调用。props和state可能会异步更新,所以你不要依赖他们的值来更新下一个状态。使用函数的形式,这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数,我们可以在这个值基础上修改。
this.setState((state, props) => ({
num: state.num + props.num
}))
1.6 列表渲染
const list = ['Tifa', 'Cloud', 'Aerith', 'Vincent']
const seventhHeaven = (
<ul>
{
list.map((item, index) => (
<li key={index}>
{item} --- {index}
</li>
))
}
</ul>
)
ReactDOM.render(seventhHeaven, document.getElementById('root'))
1.7 条件渲染
- 根据条件返回不同的结构
function TifaOrAerith (props) {
if (props.isTifa === true) {
return <h2>Tifa~~~</h2>
}else{
return <h2>Aerith~~~</h2>
}
}
ReactDOM.render(<TifaOrAerith isTifa={true} />, document.getElementById('root'))
- 根据条件返回不同的组件
function Tifa (props) {
return <h2>Tifa~~~</h2>
}
function Aerith (props) {
return <h2>Aerith~~~</h2>
}
function TifaOrAerith (props) {
if (props.isTifa === true) {
return <Tifa />
}else{
return <Aerith />
}
}
ReactDOM.render(<TifaOrAerith isTifa={true} />, document.getElementById('root'))
- 使用 && 运算符
function TifaIsMine (props) {
return <h2>Tifa is mine~~~</h2>
}
class Tifa extends React.Component {
constructor (props) {
super(props)
this.state = {
isTifa: true,
msg: 'Tifa is mine~~~'
}
}
render () {
const { isTifa, msg } = this.state
return (
<div>
<h2>悄悄的告诉你</h2>
{isTifa && <h2>{msg}</h2>}
{/* {isTifa && <TifaIsMine />} */}
</div>
)
}
}
ReactDOM.render(<Tifa />, document.getElementById('root'))
1.8 表单
    表单元素对应着数据,我们想实现双向数据绑定的效果,需要在表单元素上绑定onchange事件,state中的值改变表单元素的值随之改变。这种表单的值和state中的值进行绑定的组件叫做受控组件。
class MyForm extends React.Component {
constructor (props) {
super(props)
this.state = {
username: '',
password: ''
}
}
// e是事件对象
// e.target指的是发生事件的元素
changeValue = e => {
this.setState({
[e.target.name]: e.target.value
})
}
render () {
const { username, password } = this.state
return (
<form>
<div>
<label>用户名:</label> {username}
<br />
<input
type='text'
name='username'
value={username}
onChange={this.changeValue}
/>
</div>
<div>
<label>密码:</label> {password}
<br />
<input
type='text'
name='password'
value={password}
onChange={this.changeValue}
/>
</div>
</form>
)
}
}
1.9 Ref
    获取子组件实例或者dom元素可以用ref来实现,Refs 是使用 React.createRef() 创建,在构造组件时,通常将 Refs 分配给实例属性,以便可以在整个组件中引用它们。
class Myform extends React.Component {
constructor (props) {
super(props)
this.state = {}
// 在组件初始化的时候创建一个ref对象
this.myref = React.createRef()
}
// 在组件挂载到页面之后自动执行的方法
componentDidMount () {
// 让输入框获得焦点
this.myref.current.focus()
}
render () {
return (
// 在标签中通过ref关联创建的ref对象
<input type='text' ref={this.myref} />
)
}
}
// 用箭头函数 ❤❤❤
class Myform extends React.Component {
componentDidMount () {
this.myRef.focus()
}
render () {
return <input ref={ ele => this.myRef = ele } />
}
}
// 这个就看看吧↓
class Myform extends React.Component {
componentDidMount () {
this.refs.myRef.focus()
}
render () {
return <input ref='myRef' />
}
}
2.0 生命周期
常用的生命周期方法
- constructor: 在组件初始化的时候会自动执行
- render: 这个方法会在组件开始执行,也会在state和props的数据发生变化时执行
- componentDidMount: 组件加载完成,能够获取真是的 DOM 在此阶段可以发ajax请求、绑定事件
- componentDidUpdate: 这个方法在组件更新之后执行
- componentWillUnmount: 这个方法在组件卸载和销毁之前直接调用,此阶段可以清除定时器、事件绑定
    不常用的生命周期方法 shouldComponentUpdate ,这个方法在组件更新之前执行,不过这个方法可以决定是否要更新组件,它需要返回一个布尔值,如果返回一个true,进入render方法,然后接着进入 componentDidUpdate 方法,如果返回一个false,那么执行会停留在当前方法上,视图就不会更新了。
2.1 用脚手架
脚手架工具为create-react-app,临时安装create-react-app工具并且生成项目包my-app
npx create-react-app my-app
cd my-app
npm start
2.2 数据传递
- 父子组件传值
- 父组件向子组件传值,可以使用子组件的props属性,将父组件的值传进去。
- 子组件向父组件传值,可以使用子组件的props属性,将父组件的一个方法的引用传递到子组件中,子组件调用这个方法,将子组件中的数据传递出来给父组件
import React, { Component } from 'react'
class Father extends Component {
constructor (props) {
super(props)
this.state = {
name: 'Tifa',
age: 18
}
}
// 传递给子组件的函数
giveSonFunc = num => {
this.setState({
age: num
})
}
render () {
const { name, age } = this.state
return (
<>
<h2>这是父组件</h2>
<p>子组件传递过来的年龄:{ age }</p>
<hr />
<Son
name={ name }
giveAppData={ this.giveSonFunc }
/>
</>
)
}
}
class Son extends Component {
render () {
return (
<>
<h2>这是子组件</h2>
<p>父组件传递过来的名字:{ this.props.name }</p>
{/* 通过函数的参数向父组件传递值 */}
<button onClick={ () => { this.props.giveAppData(20) } }>点我传值给父组件</button>
</>
)
}
}
- 兄弟组件传值
使用events模块,实例化EventEmitter类,通过emit方法来发送数据,通过on方法来接收数据。
import React, { Component } from 'react'
import { EventEmitter } from 'events'
// 实例化bus对象,用于组件间的传值
const bus = new EventEmitter()
class App extends Component {
render () {
return (
<>
<Tifa />
<hr />
<Aerith />
</>
)
}
}
class Tifa extends Component {
constructor (props) {
super(props)
this.state = {
name: ''
}
}
componentDidMount () {
// 接收值
bus.on('xxx', data => {
this.setState({ name: data.name })
})
}
render () {
return (
<>
<h2>蒂法组件</h2>
<p>艾莉丝组件传递过来的名字:{ this.state.name } </p>
</>
)
}
}
class Aerith extends Component {
giveTifaName = () => {
// 传递值
bus.emit('xxx', { name: 'Aerith' })
}
render () {
return (
<>
<h2>艾莉丝组件</h2>
<button onClick={ this.giveTifaName }>点我传递名字给蒂法组件</button>
</>
)
}
}
export default App
2.3 PropTypes类型检查
    PropTypes 提供很多验证器 (validator) 来验证组件接收到的数据类型是有效的。当向 props 传入无效数据时,JavaScript 控制台会抛出警告。
// 安装校验规则包
npm i prop-types
import propTypes from 'prop-types'
class App extends Component {
render () {
return (
<>
<h3>propTypes校验</h3>
{/* 数值类型 */}
<p>{ this.props.num }</p>
{/* 数组类型 */}
<p>{ this.props.arr }</p>
{/* 函数类型 */}
<p>{ this.props.func }</p>
{/* 多个值中的一个 */}
<p>{ this.props.enum }</p>
{/* 多个类型中的一个 */}
<p>{ this.props.union }</p>
</>
)
}
}
App.propTypes = {
num: propTypes.number.isRequired,
arr: propTypes.array,
func: propTypes.func,
enum: propTypes.oneOf(['Tifa', 'Aerith']),
union: propTypes.oneOfType([
propTypes.string,
propTypes.number
])
}
// 校验错误的在控制台 console 中反馈
// 常用的校验类型:array、bool、func、number、object、string
// 必填项:isRequired
// 多个值中一个:oneof
ReactDOM.render(<App
num={ 111 }
enum='Tifa'
union='222'
/>, document.getElementById('root'))
    组件的props中的属性值可以不传入,然后通过配置特定的 defaultProps 属性来定义 props 的默认值
class App extends Component {
render () {
return (
<>
<p>{ this.props.name }</p>
</>
)
}
}
App.defaultProps = {
name: 'Tifa'
}
2.4 Render Props
    如果多个组件的部分功能相同,我们可以将这些部分相同的功能封装成一个组件,再通过一定的模式,将这个组件的功能赋予其他组件,从而达到组件功能复用的目的,复用组件的功能,其实就是复用组件的state以及操作state的方法,我们可以用render-props模式,也可以使用高阶组件(HOC)。
class App extends Component {
render () {
return (
<>
<h3>Render Props模式</h3>
<Tifa xxx={ ({ name }) => <p>名字:{ name } </p> } />
</>
)
}
}
class Tifa extends React.Component {
constructor (props) {
super(props)
this.state = {
name: 'Tifa'
}
}
render () {
return this.props.xxx(this.state)
}
}
使用children属性来实现
class App extends Component {
render () {
return (
<>
<h3>Render Props模式</h3>
{/* <Tifa xxx={ state => <ShowName {...state} /> } /> */}
{/* ↓↓↓使用children属性↓↓↓ */}
<Tifa>{ state => <ShowName {...state} /> }</Tifa>
</>
)
}
}
class Tifa extends React.Component {
constructor (props) {
super(props)
this.state = {
name: 'Tifa'
}
}
render () {
// return this.props.xxx(this.state)
// ↓↓↓使用children属性↓↓↓
return this.props.children(this.state)
}
}
const ShowName = ({ name }) => <p>名字:{ name } </p>
2.5 高阶组件(HOC)
    组件功能复用还可以使用高阶组件(HOC, Higher-Order Component),高阶组件是一个函数,它接收一个组件,返回 增加了功能 的组件。高阶组件内部创建了一个组件,组件内部提供可复用的状态和逻辑,传入的组件作为它的子组件,组件通过props属性将状态传递给这个子组件。最终这个函数将组件返回,返回的组件相当于是在传入的组件基础上增加了额外的功能。
// 定义一个高阶组件
function Hoc (Comp) {
class Tifa extends Component {
constructor (props) {
super(props)
this.state = {
name: 'Tifa'
}
}
render () {
return <Comp { ...this.state } {...this.props} />
}
}
return Tifa
}
const ShowNameAge = ({ name, age }) => <p>名字:{ name } -- 年龄:{ age } </p>
// 传递一个组件
const App = Hoc(ShowNameAge)
ReactDOM.render(<App age='18'/>, document.getElementById('root'))
2.6 React路由
通过react-router-dom模块来实现路由
// 安装包
npm i react-router-dom
// 导入对应的依赖
import { HashRouter, Route, Link, Switch, Redirect } from 'react-router-dom'
- HashRoter: 哈希路由,路由组件的容器标签
- Route: ★ 组件的容器标签,path属性和Link的to属性的地址对应,component对应相应的组件
- Link: 路由的链接,通过to属性来定义链接地址,最终呈现为a标签
- Switch: 如果需要定义404跳转和重定向跳转,需要用此标签包裹Route标签
- Redirect: 路由重定向,通过from属性定义原始路由,通过to属性定义重定向路由
// 基本的使用
import { HashRouter, Link, Route, Switch, Redirect } from 'react-router-dom'
function Tifa () {
return <h2>我是Tifa</h2>
}
function Aerith () {
return <h2>我是Aerith</h2>
}
function Cloud () {
return <h2>我是Cloud</h2>
}
function NotFound () {
return <h2>404 Not Found</h2>
}
class App extends Component {
render () {
return (
<HashRouter>
<Link to="/">蒂法</Link><br/>
<Link to="/aerith">艾丽丝</Link><br/>
<Link to="/cloud">克劳德</Link>
<hr />
{/* Switch包裹路由, 匹配到就不往下匹配, NotFound要放在最下面 */}
<Switch>
{/* exact属性是让path精确匹配,否则"/"和"/tifa"的都会匹配 */}
{/* <Route exact path="/" component={ Tifa } /> */}
<Route path="/tifa" component={ Tifa } />
<Route path="/aerith" component={ Aerith } />
<Route path="/cloud" component={ Cloud } />
{/* 重定向要写在404上面和路由的最下面 */}
<Redirect exact from="/" to="/tifa" />
{/* 404页面 */}
<Route component={ NotFound } />
</Switch>
</HashRouter>
)
}
}
还可以自定义一个路由组件,我们使用Route组件的children属性,这样可以根据路由的匹配 动态调整界面
// 将上面的略作修改
// 先自定义一个路由组件
function CustomLink ({ label, exact, to }) {
return (
<Route
path={to}
exact={exact}
// 不论path匹配与否都想渲染些东西时,可以使用children属性
// match由系统传入, 匹配当前路由则返回一个对象, 不匹配返回null
children={
sb => {
return (
<Link
to={to}
style={ sb.match?{color:'lightgreen'}:{color:'cyan'} }
>
{ label }
</Link>
)
}
}
/>
)
}
// 替换上面的<Link />,这样再点击链接时会更改样式
<CustomLink label="蒂法" to="/tifa" />
<CustomLink label="艾丽丝" to="/aerith" />
<CustomLink label="克劳德" to="/cloud" exact={ true } />
路由跳转,编程式导航的实现
this.props.history.push('/tifa')
路由传参,1. 通过params的方式传参/:xxx,通过props属性来获取路由传递过来的参数props.match.params.xxx; 2. 通过编程式导航携带
// 动态路由的方式params
function Tifa (props) {
return <h2>收到的名字是: {props.match.params.name}</h2>
}
<Route path="/ff7/:name" component={ Tifa } />
// query的方式↓
// props.history.push({ path: '/ff7', query: { name: 'Tifa'} })
// props.location.query.name 接收参数
// 编程时导航的方式,最好用state传递/接收参数
// 通过props.history.push({})来跳转并传递参数
function Tifa (props) {
return (
<>
<h2>我是Tifa</h2>
<button onClick={()=>props.history.push({
pathname: '/aerith',
state: {
name: 'Tifa'
}
})}>去找Aerith</button>
</>
)
}
// 通过props.history.location.state来接收参数
function Aerith (props) {
return (
<>
<h2>我是Aerith</h2>
<p>谁要过来: { props.history.location.state && props.history.location.state.name }</p>
</>
)
}
非路由组件获取props.history用withRouter方法包一下,withRouter会将history,location,match对象传到props上面
经常会用到子路由,直接在子组件使用路由,不用使用<HashRouter>包裹
function Buster () {
return <p>Buster</p>
}
function Sword () {
return <p>Sword</p>
}
function Cloud () {
return (
<>
<h2>我是Cloud</h2>
<Link to="/cloud/buster">Buster</Link><br />
<Link to="/cloud/sword">Sword</Link>
<Route exact path="/cloud/buster" component={Buster} />
<Route exact path="/cloud/sword" component={Sword} />
</>
)
}
class App extends Component {
render () {
return (
<HashRouter>
<Link to="/cloud">Cloud</Link>
<hr />
{/* 可以将component={()=><Cloud></Cloud>} */}
<Route path="/cloud" component={ Cloud } />
</HashRouter>
)
}
}
// component={()=>(<Cloud>Cloud里面的东西都可以放到这里</Cloud>)}
// 然后Cloud里面用props.children替换
2.7 Redux
    简单地说就是公共状态管理的工具,类似于vuex。对于复杂的组件关系,组件间的数据传递就变得非常复杂难以维护,redux就是一种解决方法,将数据放在一个地方进行集中管理,所有的组件都共享这一个地方的数据。
Redux基本流程:(参考阮一峰大大的教程)
- 用户发出Action
store.dispatch(action) - Store自动调用Reducer,并且传入两个参数:当前State和收到的Action。Reducer会返回新的State。
- State一旦有变化,Store就会调用监听函数。
store.subscribe(listener) - listener可以通过store.getState()得到当前状态。
listerner: () => {
this.setState(store.getState())
}
建一个store文件夹,并创建store和reducer
// index.js
import { createStore } from 'redux'
import reducer from './reducer'
const store = createStore(reducer)
export default store
// reducer.js 用于更新状态。dispatch发过来的action都会通过这里来改变store状态
// state参数起始存放的是原始数据
// action参数是一个对象,由store传递进来的数据变更
const reducer = (state, action) => {
return state
}
export default reducer
用一个代码片段来实现流程
// 导入建好的store
import store from './store'
class App extends Component {
constructor (props) {
super(props)
this.state = {
name: store.getState().name || 'Aerith',
}
// 订阅来自store的通知,有通知则出发storeChange函数
// 这个订阅会返回一个函数,可以用来解除订阅
this.unsubscribe = store.subscribe(this.storeChange)
}
storeChange = () => {
this.setState({
name: store.getState().name
})
}
changeValue = name => {
store.dispatch({
type: 'change_name',
value: name
})
}
componentWillUnmount () {
this.unsubsribe() // 解除订阅
}
render () {
const goddess = 'Tifa'
const { name } = this.state
return (
<>
<h3>Redux</h3>
<p>{ name }</p>
<input type="button" value="点我改变名字" onClick={ () => this.changeValue(goddess) } />
</>
)
}
}
// 给state一个初始值
const reducer = (state={}, action) => {
if (action.type === 'change_name') {
// 克隆一个state的副本
const newState = JSON.parse(JSON.stringify(state))
newState.name = action.value
return newState
}
return state
}
2. 待补充...
(HOOK)