react提供了一个用于创建react项目的脚手架库:create-react-app
项目的整体技术架构为:react+webpack+es6+eslint
第一步,全局安装:npm i -g create-react-app
第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
第三步,进入项目文件夹:cd hello-react
第四步,启动项目:npm start
public——静态资源文件夹
favicon.icon——网站页签图标
index.html——主页面
logo192.png——logo图
logo512.png——logo图
manifest.json——应用加壳的配置文件
robots.txt——爬虫协议文件
src——源码文件夹
App.css——App组件的样式
App.js——App组件
App.test.js——用于给App做测试
index.css——样式
index.js——入口文件
logo.svg——logo图
reportWebVitals.js——页面性能分析文件(需要web-vitals库的支持)
setupTests.js——组件单元测试的文件(需要jest-dom库的支持)
1)拆分组件、实现静态组件,注意:className、style的写法
2)动态初始化列表,如何确定将数据放在哪个组件的state中?
3)关于父子之间通信:
父组件:
import React, { Component } from 'react'
import Header from './components/Header/index'
import List from './components/List/index'
import Footer from './components/Footer/index'
import './App.css'
//创建并暴露App组件
export default class App extends Component {
//初始化状态
state = {
todos: [
{ id: '001', name: '吃饭', done: true },
{ id: '002', name: '睡觉', done: true },
{ id: '003', name: '打代码', done: false }
]
}
addTodo = (todoObj) => {
const { todos } = this.state
const newTodos = [todoObj, ...todos]
this.setState({ todos: newTodos })
}
updateTodo = (id, done) => {
const { todos } = this.state
const newTodos = todos.map(todoObj => {
if (todoObj.id === id) {
return { ...todoObj, done: done }
} else {
return todoObj
}
})
this.setState({ todos: newTodos })
}
deleteTodo = (id) => {
const { todos } = this.state
const newTodos = todos.filter((todoObj) => {
return todoObj.id !== id
})
this.setState({ todos: newTodos })
}
checkAllTodo = (done) => {
const { todos } = this.state
const newTodos = todos.map((todoObj) => {
return { ...todoObj, done: done }
})
this.setState({ todos: newTodos })
}
clearAllDone = () => {
const { todos } = this.state
const newTodos = todos.filter((todoObj) => {
return !todoObj.done
})
this.setState({ todos: newTodos })
}
render() {
const { todos } = this.state
return (
);
}
}
Header子组件:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { nanoid } from 'nanoid'
import './index.css'
export default class Header extends Component {
static propTypes = {
addTodo: PropTypes.func.isRequired
}
handleKeyUp = (event) => {
const { target, keyCode } = event
const { addTodo } = this.props
if (keyCode !== 13) {
return
}
if (target.value.trim() === '') {
alert('输入不能为空')
return
}
const todoObj = { id: nanoid(), name: target.value, done: false }
addTodo(todoObj)
target.value = ''
}
render() {
return (
)
}
}
List子组件:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Item from '../Item/index'
import './index.css'
export default class List extends Component {
static propTypes = {
todos: PropTypes.array.isRequired,
updateTodo: PropTypes.func.isRequired,
deleteTodo: PropTypes.func.isRequired
}
render() {
const { todos, updateTodo, deleteTodo } = this.props
return (
{
todos.map(todo => {
return
})
}
)
}
}
Item子组件:
import React, { Component } from 'react'
import './index.css'
export default class Item extends Component {
state = {
mouse: false
}
handleMouse = (flag) => {
return () => {
this.setState({ mouse: flag })
}
}
handleCheck = (id) => {
const { updateTodo } = this.props
return (event) => {
updateTodo(id, event.target.checked)
}
}
handleDelete = (id) => {
const { deleteTodo } = this.props
if (window.confirm('确定删除吗?')) {
deleteTodo(id)
}
}
render() {
const { id, name, done } = this.props
const { mouse } = this.state
return (
)
}
}
Footer子组件:
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import './index.css'
export default class Footer extends Component {
static propTypes = {
todos: PropTypes.array.isRequired,
checkAllTodo: PropTypes.func.isRequired,
clearAllDone: PropTypes.func.isRequired
}
handleCheckAll = (event) => {
const { checkAllTodo } = this.props
checkAllTodo(event.target.checked)
}
handleClearAllDone = () => {
const { clearAllDone } = this.props
clearAllDone()
}
render() {
const { todos } = this.props
const doneCount = todos.reduce((pre, todo) => { return pre + (todo.done ? 1 : 0) }, 0)
const total = todos.length
return (
已完成{doneCount} / 全部{total}
)
}
}
4)注意defaultChecked和checked的区别,类似的还有:defaultValue和value
5)状态在哪里,操作状态的方法就在哪里
什么是路由?
路由分类:
a.后端路由:
理解:value是function,用来处理客户端提交的请求
注册路由:router.get(path, function(req, res))
工作过程:当node接收到一个请求时,根据请求路径找到匹配的路由,调用路由中的函数来处理请求,返回响应数据
b.前端路由:
浏览器端路由,value是component,用于展示页面内容
注册路由:
工作过程:当浏览器的path变为/test时,当前路由组件就会变为Test组件
yarn add react-router-dom
Demo
的最外侧包裹了一个
或
App.jsx:
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Home from './pages/Home/index'
import About from './pages/About/index'
export default class App extends Component {
render() {
return (
React Router Demo
{/* 编写路由链接 */}
About
Home
{/* 注册路由 */}
)
}
}
index.js:
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
,
document.getElementById('root')
)
1)写法不同
一般组件:
路由组件:
2)存放位置不同
一般组件:components
路由组件:pages
接收到的props不同:
一般组件:写组件标签时传递了什么,就能收到什么
路由组件:接收到三个固定的属性
history:
go: ƒ go(n)
goBack: ƒ goBack()
goForward: ƒ goForward()
push: ƒ push(path, state)
replace: ƒ replace(path, state)
location:
pathname: "/about"
search: ""
state: undefined
match:
params: {}
path: "/about"
url: "/about"
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom'
import './index.css'
export default class MyNavLink extends Component {
static propTypes = {
to: PropTypes.string.isRequired,
children: PropTypes.string.isRequired
}
render() {
return (
)
}
}
App.jsx:
import React, { Component } from 'react'
import { Route } from 'react-router-dom'
import MyNavLink from './components/MyNavLink/index'
import Home from './pages/Home/index'
import About from './pages/About/index'
export default class App extends Component {
render() {
return (
React Router Demo
{/* 编写路由链接 */}
Home
About
{/* 注册路由 */}
)
}
}
App.jsx:
import React, { Component } from 'react'
import { Route, Switch } from 'react-router-dom'
import MyNavLink from './components/MyNavLink/index'
import Home from './pages/Home/index'
import About from './pages/About/index'
export default class App extends Component {
render() {
return (
React Router Demo
{/* 编写路由链接 */}
Home
About
{/* 注册路由 */}
)
}
}
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
路由链接(携带参数):
{msgObj.title}
注册路由(声明接收):
接收参数:this.props.match.params
父组件:
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail/index'
export default class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' }
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map((msgObj) => {
return (
-
{/* 向路由组件传递params参数 */}
{msgObj.title}
)
})
}
{/* 声明接收params参数 */}
)
}
}
子组件:
import React, { Component } from 'react'
const detailData = [
{ id: '01', content: '消息1详情' },
{ id: '02', content: '消息2详情' },
{ id: '03', content: '消息3详情' }
]
export default class Detail extends Component {
render() {
//接收params参数
const { id, title } = this.props.match.params
const findResult = detailData.find((detailObj) => {
return detailObj.id === id
})
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
路由链接(携带参数):
{msgObj.title}
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.search
备注:获取到的search是urlencoded编码字符串,需要借助querystring解析
父组件:
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail/index'
export default class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' }
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map((msgObj) => {
return (
-
{/* 向路由组件传递search参数 */}
{msgObj.title}
)
})
}
{/* search参数无需声明接收,正常注册路由即可 */}
)
}
}
子组件:
import React, { Component } from 'react'
import qs from 'querystring'
const detailData = [
{ id: '01', content: '消息1详情' },
{ id: '02', content: '消息2详情' },
{ id: '03', content: '消息3详情' }
]
export default class Detail extends Component {
render() {
//接收search参数
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))
const findResult = detailData.find((detailObj) => {
return detailObj.id === id
})
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
路由链接(携带参数):
{msgObj.title}
注册路由(无需声明,正常注册即可):
接收参数:this.props.location.state
备注:刷新也可以保留住参数
父组件:
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Detail from './Detail/index'
export default class Message extends Component {
state = {
messageArr: [
{ id: '01', title: '消息1' },
{ id: '02', title: '消息2' },
{ id: '03', title: '消息3' }
]
}
render() {
const { messageArr } = this.state
return (
{
messageArr.map((msgObj) => {
return (
-
{/* 向路由组件传递state参数 */}
{msgObj.title}
)
})
}
{/* state参数无需声明接收,正常注册路由即可 */}
)
}
}
子组件:
import React, { Component } from 'react'
const detailData = [
{ id: '01', content: '消息1详情' },
{ id: '02', content: '消息2详情' },
{ id: '03', content: '消息3详情' }
]
export default class Detail extends Component {
render() {
//接收state参数
const { id, title } = this.props.location.state || {}
const findResult = detailData.find((detailObj) => {
return detailObj.id === id
}) || {}
return (
- ID:{id}
- TITLE:{title}
- CONTENT:{findResult.content}
)
}
}
借助this.prosp.history
对象上的API对操作路由跳转、前进、后退
this.prosp.history.push()
this.prosp.history.replace()
this.prosp.history.goBack()
this.prosp.history.goForward()
this.prosp.history.go()
底层原理不一样
BrowserRouter使用的是H5的history API,不兼容IE9及以下版本
HashRouter使用的是URL的哈希值
path表现形式不一样
BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
HashRouter的路径包含#,例如:localhost:3000/#/demo/test
刷新后对路由state参数的影响
BrowserRouter没有任何影响,因为state保存在history对象中
HashRouter刷新后会导致路由state参数的丢失
备注:HashRouter可以用于解决一些路径错误相关的问题
视频资料:
https://www.bilibili.com/video/BV1wy4y1D7JT
源码地址:
https://github.com/hxt970311/react_demo