赞
踩
/css 就相当于 public 中的css 文件夹。/ 就是 public
1、全局安装 react
npm i -g create-react-app
2、创建项目
切换到想要创建项目的目录
create-react-app 项目名
eg: create-react-app hello-react
3、启动测试
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库的支持)
将脚手架自动生成的 public 、src 文件删除(移动到其他地方)
创建 public、src 文件夹
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
// 引入核心库
import React from 'react'
// 引入 ReactDOM
import ReactDOM from 'react-dom'
// 引入 app 组件(.js/.jsx 后缀可以省略)
import App from './App'
// 渲染页面
ReactDOM.render(<App/>,document.getElementById("root"))
// 创建 组件 App
import {React,Component} from 'react'
import Hello from './components/Hello/Hello'
// 创建并暴露组件
export default class App extends Component{
render(){
return (
<div>
<Hello/>
</div>
)
}
}
在 src 文件夹下创建 components 文件夹,以后组件都存放在这里面了。一个组件一个文件夹。
.hello{
font-size: 20px;
color: red;
}
为了 和 原生的 js 文件区分,以后组件都使用 jsx
import {React,Component} from 'react'
import './Hello.css'
export default class Hello extends Component {
render(){
return(
<div>
<span className='hello'>Hello React !!!</span>
</div>
)
}
}
不是必要的
以后开发过程中会有很多个组件,很多个组件样式。但是他们最终都会被聚集到 App.js 组件中。所以如果多个组件中样式名称一致的话,就会产生冲突。
如果能保证所有组件中的样式名称不冲突,可以不做样式模块化
1、将 Hello.css 名称修改为 Hello.module.css
2、修改 jsx 中的代码
import {React,Component} from 'react'
import hello from './Hello.module.css'
export default class Hello extends Component {
render(){
return(
<div>
<span className={hello.hello}>Hello React !!!</span>
</div>
)
}
}
写代码会有提示,以及一些代码片段
该案例,需要将组件分为四个 :Header、List、Item、Footer
import React,{Component} from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App/>,document.getElementById("root"))
import React, { Component } from 'react' import Header from './components/Header/Header' import Footer from './components/Footer/Footer' import List from './components/List/List' import './App.css' export default class App extends Component { state = {todos:[ {id:'01',name:'java',done:true}, {id:'02',name:'react',done:false}, {id:'03',name:'vue',done:true} ]} // 用于添加 todo ,接收的参数是 todo 对象 addToDo = (todo)=>{ // 获取原 todos const {todos} = this.state // 追加一个todo const newTodos = [todo,...todos] this.setState({todos:newTodos}) } // 修改 todo 是否选中 updateTodo = (id,done)=> { // 获取状态中的 todos const {todos} = this.state // 遍历 todos 处理数据 const newtodo = todos.map((todo)=>{ if(todo.id === id) return {...todo,done} else return todo }) // 更新数据 this.setState({todos:newtodo}) } // 删除 todo 对象 deleteTodo = (id) => { // 获取原来的 todos const {todos} = this.state // 删除指定 id 的 todo 对象 const newTodo = todos.filter((todo)=>{ return todo.id !== id }) this.setState({todos:newTodo}) } // 全选功能 checkAll = (done) => { // 获取原来的 todos const {todos} = this.state // 处理数据 const newTodo = todos.map((todo)=>{ return {...todo,done} }) // 更新状态中的值 this.setState({todos:newTodo}) } // 清除所有已完成的 todo deleteAllTodo = ()=>{ // 获取原来的数据 const {todos} = this.state // 加工数据 const newTodo = todos.filter((todo)=>{ return todo.done === false }) // 更新状态数据 this.setState({todos:newTodo}) } render() { return ( <div className="todo-container"> <div className="todo-wrap"> <Header addToDo={this.addToDo}/> <List handleCheck={this.updateTodo} deleteTodo={this.deleteTodo} todos={this.state.todos}/> <Footer deleteAllTodo={this.deleteAllTodo} checkAll={this.checkAll} todos={this.state.todos}/> </div> </div> ) } }
/*base*/ body { background: #fff; } .btn { display: inline-block; padding: 4px 12px; margin-bottom: 0; font-size: 14px; line-height: 20px; text-align: center; vertical-align: middle; cursor: pointer; box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); border-radius: 4px; } .btn-danger { color: #fff; background-color: #da4f49; border: 1px solid #bd362f; } .btn-danger:hover { color: #fff; background-color: #bd362f; } .btn:focus { outline: none; } .todo-container { width: 600px; margin: 0 auto; } .todo-container .todo-wrap { padding: 10px; border: 1px solid #ddd; border-radius: 5px; }
import React, { Component } from 'react' import PropTypes from 'prop-types' import {nanoid} from 'nanoid' import './Header.css' export default class Header extends Component { // 对接收的 props 进行类型必要性限制 static propTypes={ addToDo:PropTypes.func.isRequired } handleKey = (event) => { const { target, keyCode } = event // 回车键的 code 值为13,如果不是按回车则不进行操作 if (keyCode !== 13) return // 添加的 todo 名字不能为空 if(target.value.trim() === ''){ alert("输入不能为空") return } // 准备一个todo对象 const todo = {id:nanoid(),name:target.value,done:false} // 清空文本框 target.value = '' // 将 todo 对象传给父组件(App) this.props.addToDo(todo) } render() { return ( <div className="todo-header"> <input type="text" onKeyUp={this.handleKey} placeholder="请输入你的任务名称,按回车键确认" /> </div> ) } }
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
}
import React, { Component } from 'react' import PropTypes from 'prop-types' import Item from '../Item/Item' import './List.css' export default class List extends Component { // 对接收的 props 进行类型必要性限制 static propTypes={ todos:PropTypes.array.isRequired, handleCheck:PropTypes.func.isRequired, deleteTodo:PropTypes.func.isRequired } render() { const {todos,handleCheck,deleteTodo} = this.props return ( <ul className="todo-main"> { todos.map(todo=>{ return <Item handleCheck={handleCheck} deleteTodo={deleteTodo} {...todo} key={todo.id}/> }) } </ul> ) } }
/*main*/ .todo-main { margin-left: 0px; border: 1px solid #ddd; border-radius: 2px; padding: 0px; } .todo-empty { height: 40px; line-height: 40px; border: 1px solid #ddd; border-radius: 2px; padding-left: 5px; margin-top: 10px; }
import React, { Component } from 'react' import './Item.css' export default class Item extends Component { state = {mouse:false} // 鼠标移入、移除的回调 handleMouse = (flag)=>{ return () => { this.setState({mouse:flag}) } } // 勾选、取消勾选某一个 todo 回调 handleCheck = (id) => { return (event) =>{ this.props.handleCheck(id,event.target.checked) } } // 删除一个 todo handleDelete = (id) => { return ()=>{ // window.confirm("确认删除吗?") 点击确认返回true,点击取消返回 false if(window.confirm("确认删除吗?")){ this.props.deleteTodo(id) } } } render() { const {id,name,done} = this.props const {mouse} = this.state return ( <li style={{backgroundColor:mouse?'#ddd':'white'}} onMouseEnter={this.handleMouse(true)} onMouseLeave={this.handleMouse(false)}> <label> <input type="checkbox" onChange={this.handleCheck(id)} checked={done}/> <span>{name}</span> </label> <button onClick={this.handleDelete(id)} className="btn btn-danger" style={{ display: mouse?'block':'none' }}>删除</button> </li> ) } }
/*item*/ li { list-style: none; height: 36px; line-height: 36px; padding: 0 5px; border-bottom: 1px solid #ddd; } li label { float: left; cursor: pointer; } li label li input { vertical-align: middle; margin-right: 6px; position: relative; top: -1px; } li button { float: right; display: none; margin-top: 3px; } li:before { content: initial; } li:last-child { border-bottom: none; }
import React, { Component } from 'react' import './Footer.css' export default class Footer extends Component { // 全选功能 handleCheckAll = (event) => { this.props.checkAll(event.target.checked) } // 清除所有已完成的 todo deleteAllDone = ()=>{ // window.confirm("确认删除吗?") 点击确认返回true,点击取消返回 false if(window.confirm("确认删除吗?")){ this.props.deleteAllTodo() } } render() { const {todos} = this.props // 已经完成的个数 const doneCount = todos.reduce((pre,todo)=>pre+(todo.done?1:0),0); //总数 const total = todos.length; return ( <div className="todo-footer"> <label> <input type="checkbox" onChange={this.handleCheckAll} checked={doneCount===total && total!==0?true:false} /> </label> <span> <span>已完成{doneCount}</span> / 全部{total} </span> <button onClick={this.deleteAllDone} className="btn btn-danger">清除已完成任务</button> </div> ) } }
/*footer*/ .todo-footer { height: 40px; line-height: 40px; padding-left: 6px; margin-top: 5px; } .todo-footer label { display: inline-block; margin-right: 20px; cursor: pointer; } .todo-footer label input { position: relative; top: -1px; vertical-align: middle; margin-right: 5px; } .todo-footer button { float: right; margin-top: 5px; }
1、拆分组件、实现静态组件。注意 :className、style 的写法
2、动态初始化列表,如何确定将数据放在那个组件的 state 中
3、关于父子之间通信 :
4、注意 defaultChecked 和 checked 区别。类似的还有 defaultValue 和 value
5、状态在哪里,操作状态的方法就在那里(this.setState())
常用的 Ajax 库 :
npm add axios
node server1.js
import React, { Component } from 'react' import axios from 'axios' export default class App extends Component { getStudent = () => { axios.get('http://localhost:5000/students').then( (response) => {console.log(response)} ); } render() { return ( <div> <button onClick={this.getStudent}>点击获取学生数据</button> </div> ) } }
在 package.json 文件中添加配置
"proxy":"http://localhost:5000"
修改 App.js 文件中代码
import React, { Component } from 'react' import axios from 'axios' export default class App extends Component { getStudent = () => { // 服务器上的端口是 5000,因为我们配置了代理,所以我们直接访问 3000,去访问代理 // 3000 为脚手架的端口 // 发送请求的时候会先在本地找资源,找不到在去服务器找 axios.get('http://localhost:3000/students').then( (response) => {console.log(response)} ); } render() { return ( <div> <button onClick={this.getStudent}>点击获取学生数据</button> </div> ) } }
在 src 目录下创建 setupProxy.js 文件,该文件不能使用 ES6 语法
固定写法 :
const {createProxyMiddleware} = require('http-proxy-middleware') module.exports = function(app) { app.use( createProxyMiddleware('/api1', { //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000) target: 'http://localhost:5000', //配置转发目标地址(能返回数据的服务器地址) changeOrigin: true, //控制服务器接收到的请求头中host字段的值 /* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000 changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:3000 changeOrigin默认值为false,但我们一般将changeOrigin值设为true */ pathRewrite: {'^/api1': ''} //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置) }), createProxyMiddleware('/api2', { target: 'http://localhost:5001', changeOrigin: true, pathRewrite: {'^/api2': ''} }) ) }
修改 App.js 文件中代码
import React, { Component } from 'react' import axios from 'axios' export default class App extends Component { getStudent = () => { axios.get('http://localhost:3000/api1/students').then( (response) => {console.log(response)} ); } getCar = () => { axios.get('http://localhost:3000/api2/cars').then( (response) => {console.log(response)} ); } render() { return ( <div> <button onClick={this.getStudent}>点击获取学生数据</button> <button onClick={this.getCar}>点击获取汽车数据</button> </div> ) } }
实现一个搜索框
该案例,需要将组件分为四个 :Header、List、Item、Footer
组件文件夹都放在 src/components 文件夹中
在 public 文件夹下创建一个 css 文件夹,将 bootstrap.css 文件引入。在 public/index.html 文件中引入样式文件
<link rel="stylesheet" href="./css/bootstrap.css">
import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
ReactDOM.render(<App/>,document.getElementById("root"))
import React, { Component } from 'react' import List from './components/List/List' import Search from './components/Search/Search' export default class App extends Component { state = { users:[],// users 初始状态 isFirst:true,// 是否为第一次打开页面 isLoding:false,// 表示是否处于加载中 err:''// 存储请求错误信息 } // 改变 State updateAppState = (stateObj) => { this.setState(stateObj) } render() { return ( <div className="container"> <Search updateAppState={this.updateAppState}/> <List {...this.state}/> </div> ) } }
import React, { Component } from 'react' import './List.css' export default class List extends Component { render() { const {users,isFirst,isLoding,err} = this.props return ( <div className="row"> { isFirst ? <h2>欢迎访问!!!</h2> : isLoding ? <h2>Loding ...... </h2> : err ? <h2>{err}</h2> : users.length===0 ? <h2>为搜索到相关用户</h2> : users.map((user) => { return ( <div key={user.id} className="card"> <a rel="noreferrer" href={user.html_url} target="_blank"> <img alt='head_photo' src={user.avatar_url} style={{ width: '100px' }} /> </a> <p className="card-text">{user.login}</p> </div> ) }) } </div> ) } }
.album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: .75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: .75rem; border-radius: 100px; } .card-text { font-size: 85%; }
import React, { Component } from 'react' import axios from 'axios' export default class Search extends Component { // 处理键盘输入的值 search = () => { // 获取用户的输入 const { value } = this.inputElement // 发送请求前通知 app 改变状态 this.props.updateAppState({isFirst:false,isLoding:true}) // 发起网络请求 axios.get(`http://localhost:3000/api1/search/users?q=${value}`).then( (response) => { // 发送请求前通知 app 改变状态 this.props.updateAppState({users:response.data.items,isLoding:false}) }, (error) => { // 发送请求前通知 app 改变状态 this.props.updateAppState({err:error.message,isLoding:false}) } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索用户</h3> <div> <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" /> <button onClick={this.search}>搜索</button> </div> </section> ) } }
工具库 :PubSubJS
npm add pubsub-js
# 订阅消息,第一个参数是消息名,第二参数是一个函数
const token = PubSub.subscribe('delete', function(data){ });
# 取消订阅
PubSub.unsubscribe(token)
# 发布消息
PubSub.publish('delete', data)
# 引入包组件
import PubSub from 'pubsub-js'
只改动了三个组件 App、Search、List
import React, { Component } from 'react'
import List from './components/List/List'
import Search from './components/Search/Search'
export default class App extends Component {
render() {
return (
<div className="container">
<Search/>
<List/>
</div>
)
}
}
import React, { Component } from 'react' import PubSub from 'pubsub-js' import axios from 'axios' export default class Search extends Component { // 处理键盘输入的值 search = () => { // 获取用户的输入 const { value } = this.inputElement // 发送请求前通知 List 组件改变状态 // this.props.updateAppState({isFirst:false,isLoding:true}) PubSub.publish('userList',{isFirst:false,isLoding:true}) // 发起网络请求 axios.get(`http://localhost:3000/api1/search/users?q=${value}`).then( (response) => { // 发送请求前通知 app 改变状态 // this.props.updateAppState({users:response.data.items,isLoding:false}) PubSub.publish('userList',{users:response.data.items,isLoding:false}) }, (error) => { // 发送请求前通知 app 改变状态 // this.props.updateAppState({err:error.message,isLoding:false}) PubSub.publish('userList',{err:error.message,isLoding:false}) } ) } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索用户</h3> <div> <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" /> <button onClick={this.search}>搜索</button> </div> </section> ) } }
import React, { Component } from 'react' import PubSub from 'pubsub-js' import './List.css' export default class List extends Component { state = { users:[],// users 初始状态 isFirst:true,// 是否为第一次打开页面 isLoding:false,// 表示是否处于加载中 err:''// 存储请求错误信息 } componentDidMount(){ this.token = PubSub.subscribe('userList',(msg,data)=>{ this.setState(data) }) } componentWillUnmount(){ PubSub.unsubscribe(this.token) } render() { const {users,isFirst,isLoding,err} = this.state return ( <div className="row"> { isFirst ? <h2>欢迎访问!!!</h2> : isLoding ? <h2>Loding ...... </h2> : err ? <h2>{err}</h2> : users.length===0 ? <h2>为搜索到相关用户</h2> : users.map((user) => { return ( <div key={user.id} className="card"> <a rel="noreferrer" href={user.html_url} target="_blank"> <img alt='head_photo' src={user.avatar_url} style={{ width: '100px' }} /> </a> <p className="card-text">{user.login}</p> </div> ) }) } </div> ) } }
fetch(`http://localhost:3000/api1/search/users?q=${value}`).then(
response => {
console.log("联系服务器成功了...")
return response.json()
}
).then(
response => {
console.log("获取数据成功了...",response)
}
).catch(
error => {
console.log("出错了...",error)
}
)
import React, { Component } from 'react' import PubSub from 'pubsub-js' export default class Search extends Component { // 处理键盘输入的值 search = async() => { // 获取用户的输入 const { value } = this.inputElement // 发送请求前通知 List 组件改变状态 // this.props.updateAppState({isFirst:false,isLoding:true}) PubSub.publish('userList',{isFirst:false,isLoding:true}) // 发起网络请求(使用 fetch 发送) try { const response = await fetch(`http://localhost:3000/api1/search/users?q=${value}`) const data = await response.json() PubSub.publish('userList',{users:data.items,isLoding:false}) } catch (error) { PubSub.publish('userList',{err:error.message,isLoding:false}) } } render() { return ( <section className="jumbotron"> <h3 className="jumbotron-heading">搜索用户</h3> <div> <input ref={c => this.inputElement = c} type="text" placeholder="输入关键词点击搜索" /> <button onClick={this.search}>搜索</button> </div> </section> ) } }
spa :单页面应用(single page web application),整个应用只有一个页面。点击页面的连接只会局部刷新。数据都需要通过 ajax 异步展示在前端页面。
一个路由就是一个映射关系。key 为路径,value 为 function(前端 :函数) 或 一个component (后端 :组件)
//方法一,直接使用H5推出的history身上的API
let history = History.createBrowserHistory()
//方法二,hash值(锚点)
let history = History.createHashHistory()
npm install react-router-dom@5.2.0
注意 :public 文件夹中的 index.html 文件是引入了 bootstrap 样式的
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
)
import React, { Component } from 'react' import { Link, Route } from 'react-router-dom' import About from './components/About/About' import Home from './components/Home/Home' export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 原生 html 中,使用 a 标签跳转页面 */} {/* <a className="list-group-item" href="./about.html">About</a> <a className="list-group-item active" href="./home.html">Home</a> */} {/* react 中,使用 路由链接实现切换组件--编写路由链接 */} <Link className="list-group-item" to="/home">Home</Link> <Link className="list-group-item" to="/about">About</Link> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </div> </div> </div> </div> </div> ) } }
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是 about 组件</h3>
)
}
}
import React, { Component } from 'react'
export default class Home extends Component {
render() {
return (
<h3>我是 Home 组件</h3>
)
}
}
1、写法不同
路由组件 :<Route path="/about" component={About} />
一般组件 :<About />
2、存放位置不同
3、接收到的 props 不同
路由组件 :接收到三个固定属性(history、location、match) history : go : f go(n) goBack : f goBack() goForward : f goForward() push : f push(path,state) replace : f replace(path,state) location : pathname : "/about" search : "" state : undefined match: params : {} path : "/about" url : "/about" 一般组件 :组件标签中传递了什么,就能接收到什么
该组件点击选中的时候会默认高亮,通过 activeClassName 属性往 className 属性上追加属性。不写该属性默认添加active 样式。
import React, { Component } from 'react' import { NavLink, Route } from 'react-router-dom' import About from './pages/About/About' import Home from './pages/Home/Home' export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 原生 html 中,使用 a 标签跳转页面 */} {/* <a className="list-group-item" href="./about.html">About</a> <a className="list-group-item active" href="./home.html">Home</a> */} {/* react 中,使用 路由链接实现切换组件--编写路由链接 */} <NavLink activeClassName='active' className="list-group-item" to="/home">Home</NavLink> <NavLink activeClassName='active' className="list-group-item" to="/about">About</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 注册路由 */} <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </div> </div> </div> </div> </div> ) } }
针对前面 路由的基本使用的案例基础上做了一些改动(App.js和新增一个自己封装的NavLink组件)
import React, { Component } from 'react' import { NavLink, Route } from 'react-router-dom' import About from './pages/About/About' import Home from './pages/Home/Home' import MyNavLink from './components/MyNavLink' export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> <Route path="/about" component={About} /> <Route path="/home" component={Home} /> </div> </div> </div> </div> </div> ) } }
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
export default class MyNavLink extends Component {
render() {
return (
// NavLink 组件标签中的标签体对应的属性是 children 。相当于 input 中的 value 属性
<NavLink activeClassName='active' className="list-group-item" to="/home" {...this.props}/>
)
}
}
import React, { Component } from 'react' import { Route, Switch } from 'react-router-dom' import About from './pages/About/About' import Home from './pages/Home/Home' import MyNavLink from './components/MyNavLink/MyNavLink' export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 这样写,默认就只会匹配到一个路由,不会出现一个路径匹配多个路由的结果 */} <Switch> <Route path="/about" component={About} /> <Route path="/home" component={Home} /> <Route path="/home" component={Test} /> </Switch> </div> </div> </div> </div> </div> ) } }
当路由地址是多级的时候,例如 :/a/b/c ,点击完路由之后在刷新页面会出现无法加载 bootstrap 样式
将 index.html 页面中样式引入路径
<link rel="stylesheet" href="./css/bootstrap.css">
该为 :
<link rel="stylesheet" href="/css/bootstrap.css">
将 index.html 页面中样式引入路径
<link rel="stylesheet" href="./css/bootstrap.css">
该为 :
<link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css">
路由的方式,将 BrowserRouter 该为 HashRouter
这样修改的话,样式引入哪里可以使用 ./
因为 HashRouter 这里使用的是锚点,# 号后面的路径都不会计入总路径,在找静态资源的时候会从 # 之前的地址为当前地址开始找。
<Route exact={true} path="/about" component={About} />
一般写在所有路由的最下方,当所有路由都无法匹配的时候,跳转至 Redirect 指定的路由
具体编码
import { Route, Switch,Redirect } from 'react-router-dom'
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<Redirect to="/about" />
</Switch>
效果 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="/css/bootstrap.css">
</head>
<body>
<div id="root"></div>
</body>
</html>
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById("root")
)
import React, { Component } from 'react' import { Route, Switch,Redirect } from 'react-router-dom' import About from './pages/About/About' import Home from './pages/Home/Home' import MyNavLink from './components/MyNavLink/MyNavLink' export default class App extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> <MyNavLink to="/about">About</MyNavLink> <MyNavLink to="/home">Home</MyNavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> {/* 这样写,默认就只会匹配到一个路由,不会出现一个路径匹配多个路由的结果 */} <Switch> <Route path="/about" component={About} /> <Route path="/home" component={Home} /> <Redirect to="/about" /> </Switch> </div> </div> </div> </div> </div> ) } }
import React, { Component } from 'react'
export default class About extends Component {
render() {
return (
<h3>我是 about 组件</h3>
)
}
}
import React, { Component } from 'react' import { Route, Switch,Redirect,NavLink } from 'react-router-dom' import Message from './Message/Message' import News from './News/News' export default class Home extends Component { render() { return ( <div> <h2>Home组件内容</h2> <div> <ul className="nav nav-tabs"> <li> <NavLink className="list-group-item" to="/home/news">News</NavLink> </li> <li> <NavLink className="list-group-item" to="/home/message">Message</NavLink> </li> </ul> <Switch> <Route path="/home/news" component={News} /> <Route path="/home/message" component={Message} /> <Redirect to="/home/news" /> </Switch> </div> </div> ) } }
import React, { Component } from 'react'
export default class News extends Component {
render() {
return (
<ul>
<li>news001</li>
<li>news002</li>
<li>news003</li>
</ul>
)
}
}
import React, { Component } from 'react' export default class Message extends Component { render() { return ( <div> <ul> <li> <a href="/message1">message001</a> </li> <li> <a href="/message2">message002</a> </li> <li> <a href="/message/3">message003</a> </li> </ul> </div> ) } }
在 Message 组件中创建一个 Detail 组件
常用指数 :params > search > state
/home/message/detail/${obj.id}/${obj.title}
}>{obj.title}参数格式
import React, { Component } from 'react' import { Link,Route } from 'react-router-dom' import Detail from './Detail/Detail' export default class Message extends Component { state = { messageArr: [ { id: 1, title: "消息1" }, { id: 2, title: "消息2" }, { id: 3, title: "消息3" } ] } render() { const {messageArr} = this.state return ( <div> <ul> { messageArr.map((obj) => { return ( <li key={obj.id}> {/* 向路由组件传递 params 参数 */} <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> </li> ) }) } </ul> <hr/> {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/} <Route path="/home/message/detail/:id/:title" component={Detail} /> </div> ) } }
import React, { Component } from 'react' const DetailData = [ {id:1,content:"容器1"}, {id:2,content:"容器2"}, {id:3,content:"容器3"} ] export default class Detail extends Component { render() { const {id,title} = this.props.match.params const findResult = DetailData.find((detailObj)=>{ return detailObj.id === Number(id) }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) } }
/home/message/detail?id=${obj.id}&title=${obj.title}
}>{obj.title}参数格式
这时候可以看得出来参数不是对象的形式,而是一个字符串,这时候我们需要 query-string 库 来将参数格式化为对象
// 将对象转为字符串
let obj = {name:"小明",age:18}
console.log(qs.stringify(obj)) // name=小明&age=18
// 将字符串转为对象
let obj2 = "name=小明&age=18";
console.log(qs.parse(obj2)) // {name:"小明",age:18}
import React, { Component } from 'react' import { Link,Route } from 'react-router-dom' import Detail from './Detail/Detail' export default class Message extends Component { state = { messageArr: [ { id: 1, title: "消息1" }, { id: 2, title: "消息2" }, { id: 3, title: "消息3" } ] } render() { const {messageArr} = this.state return ( <div> <ul> { messageArr.map((obj) => { return ( <li key={obj.id}> {/* 向路由组件传递 params 参数 */} {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */} {/* 向路由组件传递 search 参数 */} <Link to={`/home/message/detail?id=${obj.id}&title=${obj.title}`}>{obj.title}</Link> </li> ) }) } </ul> <hr/> {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/} {/* <Route path="/home/message/detail/:id/:title" component={Detail} /> */} {/* search 参数 无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail} /> </div> ) } }
import React, { Component } from 'react' import qs from 'query-string' const DetailData = [ { id: 1, content: "容器1" }, { id: 2, content: "容器2" }, { id: 3, content: "容器3" } ] export default class Detail extends Component { render() { // 接收 params 参数 // const { id, title } = this.props.match.params // 接收 search 参数 const {search} = this.props.location const {id,title} = qs.parse(search.slice(1)) const findResult = DetailData.find((detailObj) => { return detailObj.id === Number(id) }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) } }
优点是路径里不体现参数
参数格式 :
import React, { Component } from 'react' import { Link,Route } from 'react-router-dom' import Detail from './Detail/Detail' export default class Message extends Component { state = { messageArr: [ { id: 1, title: "消息1" }, { id: 2, title: "消息2" }, { id: 3, title: "消息3" } ] } render() { const {messageArr} = this.state return ( <div> <ul> { messageArr.map((obj) => { return ( <li key={obj.id}> {/* 向路由组件传递 params 参数 */} {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */} {/* 向路由组件传递 search 参数 */} {/* <Link to={`/home/message/detail?id=${obj.id}&title=${obj.title}`}>{obj.title}</Link> */} {/* 向路由组件传递 state 参数,此 state 和 组件里的 state 不是一个 */} <Link to={{pathname:'/home/message/detail',state:{id:obj.id,title:obj.title}}}>{obj.title}</Link> </li> ) }) } </ul> <hr/> {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/} {/* <Route path="/home/message/detail/:id/:title" component={Detail} /> */} {/* search 参数 无需声明接收,正常注册路由即可 */} {/* <Route path="/home/message/detail" component={Detail} /> */} {/* state 参数 无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail} /> </div> ) } }
import React, { Component } from 'react' const DetailData = [ { id: 1, content: "容器1" }, { id: 2, content: "容器2" }, { id: 3, content: "容器3" } ] export default class Detail extends Component { render() { // 接收 params 参数 // const { id, title } = this.props.match.params // 接收 search 参数 // const {search} = this.props.location // const {id,title} = qs.parse(search.slice(1)) // 接收 state 参数 const {id,title} = this.props.location.state const findResult = DetailData.find((detailObj) => { return detailObj.id === Number(id) }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) } }
是进行压栈模式,可以一直回退,会在浏览器上留下痕迹
是替换模式,不可以后退,不会再浏览器上留下痕迹
主要使用到了 history 对象中的 api
import React, { Component } from 'react' import { Link, Route } from 'react-router-dom' import Detail from './Detail/Detail' export default class Message extends Component { state = { messageArr: [ { id: 1, title: "消息1" }, { id: 2, title: "消息2" }, { id: 3, title: "消息3" } ] } replaceShow = (id, title) => { // replace 跳转 + params 参数 // this.props.history.replace(`/home/message/detail/${id}/${title}`) // replace 跳转 + search 参数 // this.props.history.replace(`/home/message/detail?id=${id}&title=${title}`) //replace 跳转 + state 参数 this.props.history.replace('/home/message/detail', { id, title }) } pushShow = (id, title) => { // push 跳转 + params 参数 // this.props.history.push(`/home/message/detail/${id}/${title}`) // push 跳转 + search 参数 // this.props.history.push(`/home/message/detail?id=${id}&title=${title}`) // push 跳转 + state 参数 this.props.history.push('/home/message/detail', { id, title }) } forward = () => { this.props.history.goForward(); } back = () => { this.props.history.goBack(); } goTo = () => { // 里面的参数为步长,正数代表前进,负数代表后退 this.props.history.go(2); } render() { const { messageArr } = this.state return ( <div> <ul> { messageArr.map((obj) => { return ( <li key={obj.id}> {/* 向路由组件传递 params 参数 */} {/* <Link to={`/home/message/detail/${obj.id}/${obj.title}`}>{obj.title}</Link> */} {/* 向路由组件传递 search 参数 */} {/* <Link to={`/home/message/detail?id=${obj.id}&title=${obj.title}`}>{obj.title}</Link> */} {/* 向路由组件传递 state 参数,此 state 和 组件里的 state 不是一个 */} <Link to={{ pathname: '/home/message/detail', state: { id: obj.id, title: obj.title } }}>{obj.title}</Link> <button onClick={() => { this.pushShow(obj.id, obj.title) }}>push 查看</button> <button onClick={() => { this.replaceShow(obj.id, obj.title) }}>replace 查看</button> </li> ) }) } </ul> <hr /> {/* 声明接收 params 参数 :id 表示接收到的参数名称叫id 可以根据情况改变*/} {/* <Route path="/home/message/detail/:id/:title" component={Detail} /> */} {/* search 参数 无需声明接收,正常注册路由即可 */} {/* <Route path="/home/message/detail" component={Detail} /> */} {/* state 参数 无需声明接收,正常注册路由即可 */} <Route path="/home/message/detail" component={Detail} /> <button onClick={this.forward}>前进</button> <button onClick={this.back}>回退</button> <button onClick={this.goTo}>go</button> </div> ) } }
import React, { Component } from 'react' import qs from 'query-string' const DetailData = [ { id: 1, content: "容器1" }, { id: 2, content: "容器2" }, { id: 3, content: "容器3" } ] export default class Detail extends Component { render() { // 接收 params 参数 // const { id, title } = this.props.match.params // 接收 search 参数 // const {search} = this.props.location // const {id,title} = qs.parse(search.slice(1)) // 接收 state 参数 const {id,title} = this.props.location.state const findResult = DetailData.find((detailObj) => { return detailObj.id === Number(id) }) return ( <ul> <li>ID:{id}</li> <li>TITLE:{title}</li> <li>CONTENT:{findResult.content}</li> </ul> ) } }
用于一般组件,因为一般组件中没有 路由组件的 三大属性,所以就无法使用 路由组件中的 history ,但是一般组件经过 withRouter 函数加工过之后,就拥有了 路由组件的 三大属性了。
具体使用
// 引入 withRouter
import { withRouter } from 'react-router-dom'
// 使用 withRouter 加工一般组件
export default withRouter(Message)
npm add antd
https://ant.design/components/overview-cn/
引入一个按钮组件
1、在页面组件上引入需要的样式组件
// 引入按钮组件
import { Button } from 'antd';
// 引入 图标 组件
import { DownloadOutlined } from '@ant-design/icons';
// 引入样式文件
import 'antd/dist/antd.css'
或
import 'antd/dist/antd.min.css'
2、使用 组件
<Button type="primary" icon={<DownloadOutlined />} >Download</Button>
如果我们在组件中 直接使用 import '/antd/dist/antd.css'
这种是将所有的样式都引进来了,例如上面的按钮案例,我们只需要一个按钮的样式即可,但是却把整个 antd 的所有组件样式都引进来了。
配置完之后再页面组件中就不需要在写 import '/antd/dist/antd.css‘
npm add react-app-rewired customize-cra
react-app-rewired : 启动脚手架
customize-cra : 更改脚手架默认的配置项
原来的 :
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
修改后的 :
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
npm add babel-plugin-import
const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
import '/antd/dist/antd.css'
去掉,启动测试Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。