为什么有
ref
,虽然官方总是不推荐使用这种破坏整体框架的api,但是实际开发,总有一些场景需要直接操作DOM元素,所以有了这个api.但是如果能不使用尽量不使用.
虽然,有种种不利,但是在一些场景确实有效并且真香~
绘图
class DrawPanel extends React.Component {
constructor(props) {
super(props)
this.state = {
value: 1
}
}
handleChange = (e) => {
this.setState({
value: e.target.value
})
}
render () {
return (
{this.state.value}
)
}
}
class DrawRing extends React.Component {
canvas = React.createRef()
constructor(props) {
super(props);
}
componentDidMount () {
this.currentValue = 0;
this.ctx = this.canvas.current.getContext('2d');
this.clear();
this.draw();
this.canvas.current.parentNode.addEventListener('resize', () => {
this.clear();
this.draw();
})
}
componentDidUpdate () {
this.clear();
this.draw();
}
clear () {
const canvas = this.canvas.current;
const parentNode = canvas.parentNode;
canvas.width = parentNode.offsetWidth;
canvas.height = parentNode.offsetHeight;
this.centerPos = [canvas.width / 2, canvas.height / 2];
}
draw () {
let { value = 10, color = 'red', duration = 1000, bgColor = '#e3e3e3', wd = 0 } = this.props,
ctx = this.ctx,
centerPos = this.centerPos,
r = 1.5 * Math.min.apply(null, centerPos) / 2,
currentValue = this.currentValue, speed = 3.6 * 10 * (value - currentValue) / duration;
speed = Math[speed >0 ? 'max': 'min'](speed > 0 ? 0.0001 : -0.0001, speed);
wd = wd || r / 5;
currentValue += speed;
if (speed > 0 && currentValue + speed > value) {
currentValue = value
}
if (speed < 0 && currentValue + speed < value) {
currentValue = value
}
ctx.beginPath();
ctx.arc(centerPos[0], centerPos[1], r, 0, Math.PI * 2 * (currentValue / 100), false);
ctx.strokeStyle = color;
ctx.lineWidth = wd;
ctx.stroke()
ctx.closePath();
ctx.beginPath();
ctx.arc(centerPos[0], centerPos[1], r, Math.PI * 2 * (currentValue / 100), Math.PI * 2, false);
ctx.strokeStyle = bgColor;
ctx.lineWidth = wd;
ctx.stroke()
ctx.closePath();
ctx.beginPath();
ctx.font = "normal normal normal " + r / 4 + "px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = color;
ctx.fillText((Math.ceil(currentValue*100)/100).toFixed(2) + "%", centerPos[0], centerPos[1])
ctx.closePath();
this.currentValue = Number(currentValue);
clearTimeout(this.timer);
if (currentValue != value) {
this.timer = setTimeout(() => {
this.clear();
this.draw();
})
}
}
render () {
return
}
}
ReactDOM.render( , document.querySelector('#root-canvas'))
ps:写这个demo过程遇到一个问题,父组件中state变化,触发子组件的props变化,到底应该在什么周期中进行操作(留到后面生命周期章节再详细分析)。
购物车动画
React.createRef
创建ref并绑定,父组件获取子组件通过传递一个函数onRef
,子组件在建立的时候通过调用此函数传递自己this
,绑定到父组件的属性上。
class Shop extends React.Component {
state = {
goods: [
{
name: "苹果",
count: 0
},
{
name: "香蕉",
count: 1
},
{
name: "樱桃",
count: 0
}
],
}
buy = (value, index, e) => {
let newGoods = this.state.goods.map(function (good) {
if (value.name == good.name) good.count += 1
return good
})
this.setState({
goods: newGoods
})
e.persist()
this.cartRef.add({
left: e.nativeEvent.x,
top: e.nativeEvent.y,
})
}
onRef = ref => {
this.cartRef = ref
}
del = (value, index) => {
let newGoods = this.state.goods.map(function (good) {
if (value.name == good.name) good.count -= 1
return good
})
this.setState({
goods: newGoods
})
}
render () {
const goods = this.state.goods,
buyGoods = goods.filter(value => {
return value.count != 0
})
return
}
}
class Goods extends React.Component {
buy = (value, index, e) => {
this.props.buy(value, index, e)
}
render () {
const data = this.props.data;
return
{
data.map((value, index) => (- { this.buy(value, index, e) }} key={index}>{value.name}{value.count}
))
}
}
}
class Cart extends React.Component {
refWrap = React.createRef()
cartRef = React.createRef()
constructor(props) {
super(props)
this.props.onRef(this);
}
del = (value, index) => {
this.props.del(value, index)
}
state = {
balls: []
}
ballId = 0
add = (ball) => {
ball = {
style: ball,
ballId: this.ballId++
}
this.setState({
balls: [
ball,
...this.state.balls
]
})
setTimeout(() => { this.animated(); }, 0)
}
del = (index) => {
if (!index) index = 0;
var newBalls = this.state.balls.map(value => value)
newBalls.pop();
this.setState({
balls: newBalls
})
}
componentDidMount () {
const rect = this.cartRef.current.getBoundingClientRect()
this.target.left = rect.x + rect.width / 2;
this.target.top = rect.y + rect.height / 2;
this.refWrap.current.addEventListener('webkitTransitionEnd', (e) => {
this.del()
})
}
target = {
left: 0,
top: 0
}
animated = () => {
if (this.state.balls.length > 0 && this.state.balls.some(value => value.left != 0)) {
var newBalls = this.state.balls.map(value => {
return Object.assign({}, { style: this.target }, {
ballId: value.ballId
})
});
this.setState({
balls: newBalls
})
console.log(this.state)
}
}
render () {
const data = this.props.data;
const balls = this.state.balls;
return
{
balls.map((ball, index) => )
}
{
data.map((value, index) => (- { this.del(value, index) }} key={index}>{value.name}{value.count}
))
}
}
}
ReactDOM.render( , document.querySelector('#root-cart'))
越级绑定ref
React.forwardRef
:首先为什么要使用这个方法,因为React中不支持想传递普通属性props
一样传递ref
这个命名的属性。
如上,在Test
组件中testname
可以通过this.props.testname
获取到父组件传递的值(传入子组件的参数)。但是ref
不同,因为ref
的作用是绑定组件或者DOM,可以通过绑定值操作组件或者DOM,所以ref
的值是它绑定的组件或者DOM,并不是传入到组件内,所以并不能使用props
属性获取。那么如果我们要传递一个ref
进入子组件怎么做呢?那么就不能使用ref,可以换个名字:
class App extends React.Component {
sonRef = React.createRef()
grandsonRef = React.createRef()
onClickHandle=()=>{
console.log(this.sonRef)
console.log(this.grandsonRef)
}
render () {
return
}
}
class Son extends React.Component {
render () {
const {diyref} = this.props;
return Son
}
}
class GrandSon extends React.Component {
render () {
console.log(this.props)
return GrandSon
}
}
ReactDOM.render( , document.querySelector('#root'))
如上,我们通过diyref
传递一个父组件定义的ref
到子组件中,然后通过子组件绑定到孙组件上。同理,我们也可以采用传递函数的方式传递一个函数子组件,然后子组件把这个函数传递到孙组件,在孙组件中调用这个函数返回自己,最终绑定到父组件的属性上。
那么除了换名称,还有其他什么方式传递ref吗,事实上有的,官方还提供了一个React.forwardRef
来做这件事。
class App extends React.Component {
sonRef = React.createRef()
grandsonRef = React.createRef()
onClickHandle=()=>{
console.log(this.sonRef)
console.log(this.grandsonRef)
}
render () {
return
}
}
正常情况下,上面的ref直接绑定了Son,所以在Son中我们获取不到ref,但是如果我希望在Son中能获取ref传递的参数,而不是把当做绑定操作,我们可以是用React.forwardRef
来定义Son:
const Son = React.forwardRef((props,ref)=>{
return Son
})
这种情况下,ref当做第二参数直接传入了,而不是绑定在组件上了。
看到这里,官网上的例子就不难理解了,结合上面,我们有更好的实践:
function logProps (Component) {
class LogProps extends React.Component {
componentDidUpdate (prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render () {
const { forwardedRef, ...rest } = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return ;
}
}
return React.forwardRef((props, ref) => {
return ;
});
}
上面调用logProps
时候加得ref
被挂载到被 LogProps 包裹的子组件上,同时利用常规 prop 属性传递ref和React.forwardRef
,而我们只需要正常的编写实际的LogProps
组件即可。