x-ref 引用元素 #

什么是 x-ref? #

x-ref 指令用于给 DOM 元素添加引用名称,通过 $refs 魔术属性可以直接访问这些元素。这在需要直接操作 DOM 时非常有用。

基本语法 #

定义引用:

html
<input x-ref="名称">

访问引用:

javascript
this.$refs.名称

基本用法 #

简单引用 #

html
<div x-data>
    <input x-ref="emailInput" type="email">
    <button @click="$refs.emailInput.focus()">聚焦输入框</button>
</div>

多个引用 #

html
<div x-data>
    <input x-ref="name" placeholder="姓名">
    <input x-ref="email" placeholder="邮箱">
    <input x-ref="phone" placeholder="电话">
    
    <button @click="$refs.name.focus()">聚焦姓名</button>
    <button @click="$refs.email.focus()">聚焦邮箱</button>
    <button @click="$refs.phone.focus()">聚焦电话</button>
</div>

在方法中使用 #

html
<div x-data="{
    focusInput() {
        this.$refs.input.focus()
    },
    clearInput() {
        this.$refs.input.value = ''
    }
}">
    <input x-ref="input" placeholder="输入内容">
    <button @click="focusInput()">聚焦</button>
    <button @click="clearInput()">清空</button>
</div>

常见用例 #

表单聚焦 #

html
<div x-data="{
    showForm: false,
    init() {
        this.$watch('showForm', (value) => {
            if (value) {
                this.$nextTick(() => this.$refs.nameInput.focus())
            }
        })
    }
}">
    <button @click="showForm = true">显示表单</button>
    
    <form x-show="showForm" @submit.prevent="showForm = false">
        <input x-ref="nameInput" placeholder="姓名">
        <input placeholder="邮箱">
        <button type="submit">提交</button>
    </form>
</div>

模态框聚焦 #

html
<div x-data="{
    open: false,
    showModal() {
        this.open = true
        this.$nextTick(() => this.$refs.closeButton.focus())
    }
}">
    <button @click="showModal()">打开模态框</button>
    
    <div x-show="open" class="modal">
        <div class="modal-content">
            <h2>模态框</h2>
            <p>内容</p>
            <button x-ref="closeButton" @click="open = false">关闭</button>
        </div>
    </div>
</div>

文件上传 #

html
<div x-data="{
    files: [],
    triggerUpload() {
        this.$refs.fileInput.click()
    },
    handleFiles(e) {
        this.files = Array.from(e.target.files)
    }
}">
    <input 
        x-ref="fileInput" 
        type="file" 
        multiple 
        hidden
        @change="handleFiles($event)"
    >
    
    <button @click="triggerUpload()">选择文件</button>
    
    <ul>
        <template x-for="file in files" :key="file.name">
            <li x-text="file.name"></li>
        </template>
    </ul>
</div>

获取输入值 #

html
<div x-data="{
    submit() {
        const name = this.$refs.name.value
        const email = this.$refs.email.value
        console.log({ name, email })
    }
}">
    <input x-ref="name" placeholder="姓名">
    <input x-ref="email" placeholder="邮箱">
    <button @click="submit()">提交</button>
</div>

操作 Canvas #

html
<div x-data="{
    draw() {
        const canvas = this.$refs.canvas
        const ctx = canvas.getContext('2d')
        ctx.fillStyle = 'blue'
        ctx.fillRect(10, 10, 100, 100)
    },
    clear() {
        const canvas = this.$refs.canvas
        const ctx = canvas.getContext('2d')
        ctx.clearRect(0, 0, canvas.width, canvas.height)
    }
}">
    <canvas x-ref="canvas" width="200" height="200" style="border: 1px solid #ccc"></canvas>
    <button @click="draw()">绘制</button>
    <button @click="clear()">清除</button>
</div>

滚动控制 #

html
<div x-data="{
    scrollToTop() {
        this.$refs.container.scrollTop = 0
    },
    scrollToBottom() {
        const el = this.$refs.container
        el.scrollTop = el.scrollHeight
    }
}">
    <div x-ref="container" style="height: 200px; overflow-y: scroll; border: 1px solid #ccc">
        <div style="height: 1000px; padding: 20px">
            <p>滚动内容区域</p>
            <p>...</p>
            <p>底部内容</p>
        </div>
    </div>
    <button @click="scrollToTop()">滚动到顶部</button>
    <button @click="scrollToBottom()">滚动到底部</button>
</div>

视频控制 #

html
<div x-data="{
    play() {
        this.$refs.video.play()
    },
    pause() {
        this.$refs.video.pause()
    },
    stop() {
        const video = this.$refs.video
        video.pause()
        video.currentTime = 0
    }
}">
    <video x-ref="video" width="320" height="240">
        <source src="movie.mp4" type="video/mp4">
    </video>
    <button @click="play()">播放</button>
    <button @click="pause()">暂停</button>
    <button @click="stop()">停止</button>
</div>

获取元素尺寸 #

html
<div x-data="{
    width: 0,
    height: 0,
    updateSize() {
        const el = this.$refs.box
        this.width = el.offsetWidth
        this.height = el.offsetHeight
    }
}" x-init="updateSize()" @resize.window="updateSize()">
    <div x-ref="box" style="width: 200px; height: 100px; background: #f0f0f0">
        尺寸: <span x-text="width"></span> x <span x-text="height"></span>
    </div>
</div>

复制文本 #

html
<div x-data="{
    text: 'Hello World',
    copied: false,
    async copy() {
        const textarea = this.$refs.clipboard
        textarea.value = this.text
        textarea.select()
        await navigator.clipboard.writeText(this.text)
        this.copied = true
        setTimeout(() => this.copied = false, 2000)
    }
}">
    <textarea x-ref="clipboard" style="position: absolute; left: -9999px"></textarea>
    <input :value="text" readonly>
    <button @click="copy()">
        <span x-show="!copied">复制</span>
        <span x-show="copied">已复制!</span>
    </button>
</div>

第三方库集成 #

html
<div x-data="{
    editor: null,
    init() {
        this.editor = new Quill(this.$refs.editor, {
            theme: 'snow'
        })
    },
    getContent() {
        return this.editor.root.innerHTML
    }
}">
    <div x-ref="editor"></div>
    <button @click="console.log(getContent())">获取内容</button>
</div>

$refs 对象 #

访问方式 #

html
<div x-data>
    <input x-ref="input1">
    <input x-ref="input2">
    
    <button @click="console.log($refs)">查看所有 refs</button>
    <button @click="console.log($refs.input1)">查看 input1</button>
</div>

动态引用 #

html
<div x-data="{
    refName: 'input',
    focus() {
        this.$refs[this.refName].focus()
    }
}">
    <input x-ref="input" placeholder="输入">
    <button @click="focus()">聚焦</button>
</div>

注意事项 #

1. 访问时机 #

$refs 只在组件渲染后可用:

html
<div x-data="{
    init() {
        console.log(this.$refs.input)
    }
}">
    <input x-ref="input">
</div>

init$refs 可能还未初始化。使用 $nextTick

html
<div x-data="{
    init() {
        this.$nextTick(() => {
            console.log(this.$refs.input)
        })
    }
}">
    <input x-ref="input">
</div>

2. v-if 影响 #

x-if 会移除元素,$refs 会变为 undefined

html
<div x-data="{ show: true }">
    <template x-if="show">
        <input x-ref="input">
    </template>
    <button @click="console.log($refs.input)">获取 ref</button>
</div>

3. x-show 不影响 #

x-show 只是隐藏元素,$refs 仍然可用:

html
<div x-data="{ show: true }">
    <input x-ref="input" x-show="show">
    <button @click="console.log($refs.input)">获取 ref</button>
</div>

4. 只读属性 #

$refs 是只读的,不应该修改:

html
<div x-data>
    <input x-ref="input">
    <button @click="$refs.input = null">
        错误:不应该修改 $refs
    </button>
</div>

最佳实践 #

1. 优先使用声明式方式 #

不推荐:

html
<div x-data>
    <input x-ref="input">
    <button @click="$refs.input.value = 'Hello'">设置值</button>
</div>

推荐:

html
<div x-data="{ value: '' }">
    <input x-model="value">
    <button @click="value = 'Hello'">设置值</button>
</div>

2. 用于必须直接操作 DOM 的场景 #

html
<div x-data>
    <input x-ref="input">
    <button @click="$refs.input.focus()">聚焦</button>
    <button @click="$refs.input.select()">全选</button>
</div>

3. 配合 $nextTick #

html
<div x-data="{
    show: false,
    focus() {
        this.show = true
        this.$nextTick(() => this.$refs.input.focus())
    }
}">
    <button @click="focus()">显示并聚焦</button>
    <input x-ref="input" x-show="show">
</div>

小结 #

x-ref 指令要点:

  • 给 DOM 元素添加引用名称
  • 通过 $refs 访问元素
  • 用于需要直接操作 DOM 的场景
  • 注意访问时机和 x-if 的影响
  • 优先使用声明式方式

至此,我们已经学习了 Alpine.js 的主要内置指令。下一章,我们将学习响应式系统。

最后更新:2026-03-28